Compare commits
1 Commits
master
...
watchtower
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf9fe24c1c |
@ -57,7 +57,7 @@ jobs:
|
||||
multiarch:
|
||||
machine:
|
||||
enabled: true
|
||||
image: default
|
||||
image: ubuntu-2204:2022.04.1
|
||||
steps:
|
||||
- run:
|
||||
command: |
|
||||
|
||||
195
.github/CODEOWNERS-HINT
vendored
195
.github/CODEOWNERS-HINT
vendored
@ -1,195 +0,0 @@
|
||||
# This file lists the owners of code in different areas of the lnd codebase
|
||||
# Codeowners will own the review for the changes being merged into their
|
||||
# respective areas
|
||||
|
||||
# aezeed
|
||||
/aezeed/ @guggero @yyforyongyu @roasbeef
|
||||
|
||||
# alias manager
|
||||
/aliasmgr/ @Crypt-iQ
|
||||
|
||||
# amp
|
||||
/amp/ @yyforyongyu @roasbeef
|
||||
|
||||
# auto pilot
|
||||
/autopilot/ @bitromortac
|
||||
|
||||
# batch
|
||||
/batch/ @bhandras
|
||||
|
||||
# block cache
|
||||
/blockcache/ @yyforyongyu @ellemouton
|
||||
|
||||
# brontide
|
||||
/brontide/ @Roasbeef @yyforyongyu @morehouse
|
||||
|
||||
# buffer
|
||||
/buffer/ @yyforyongyu
|
||||
|
||||
# build
|
||||
/build/ @Roasbeef @guggero
|
||||
|
||||
# certificates
|
||||
/cert/ @guggero
|
||||
|
||||
# chain notifications
|
||||
/chainntnfs/ @Roasbeef @yyforyongyu
|
||||
|
||||
# chain registry
|
||||
/chainreg/ @ellemouton
|
||||
|
||||
# channel acceptor
|
||||
/chanacceptor/ @Crypt-iQ
|
||||
|
||||
# channel backup
|
||||
/chanbackup/ @guggero @ellemouton
|
||||
|
||||
# channel fitness
|
||||
/chanfitness/ @yyforyongyu
|
||||
|
||||
# channel db
|
||||
/channeldb/ @Roasbeef @yyforyongyu
|
||||
|
||||
# channel notifier
|
||||
/channelnotifier/ @yyforyongyu
|
||||
|
||||
# clock
|
||||
/clock/ @bhandras
|
||||
|
||||
# cluster
|
||||
/cluster/ @bhandras
|
||||
|
||||
# command line
|
||||
/cmd/ @ellemouton
|
||||
|
||||
# contract court
|
||||
/contractcourt/ @yyforyongyu @Roasbeef @Crypt-iQ
|
||||
|
||||
# contrib
|
||||
/contrib/ @guggero
|
||||
|
||||
# discovery
|
||||
/discovery/ @ellemouton @yyforyongyu
|
||||
|
||||
# docker
|
||||
/docker/ @guggero
|
||||
|
||||
# feature
|
||||
/feature/ @ProofOfKeags
|
||||
|
||||
# functions/methods
|
||||
/fn/ @Roasbeef @ProofOfKeags
|
||||
|
||||
# funding
|
||||
/funding/ @Crypt-iQ @morehouse
|
||||
|
||||
# health check
|
||||
/healthcheck/ @guggero
|
||||
|
||||
# htlc switch
|
||||
/htlcswitch/ @Roasbeef @yyforyongyu
|
||||
|
||||
# musig2
|
||||
/internal/musig2v040/ @guggero
|
||||
|
||||
# invoices
|
||||
/invoices/ @yyforyongyu @bhandras
|
||||
|
||||
# key chain
|
||||
/keychain/ @guggero @roasbeef
|
||||
|
||||
# kvdb
|
||||
/kvdb/ @bhandras
|
||||
|
||||
# lncfg
|
||||
/lncfg/ @Roasbeef
|
||||
|
||||
# lnencrypt
|
||||
/lnencrypt/ @guggero
|
||||
|
||||
# lnpeer
|
||||
/lnpeer/ @Roasbeef
|
||||
|
||||
#lntest
|
||||
/lntest/ @yyforyongyu
|
||||
|
||||
# lntypes
|
||||
/lntypes/ @bitromortac
|
||||
|
||||
# lnutils
|
||||
/lnutils/ @yyforyongyu
|
||||
|
||||
# lnwallet
|
||||
/lnwallet/ @Roasbeef @yyforyongyu @Crypt-iQ
|
||||
|
||||
# lnwire
|
||||
/lnwire/ @ellemouton @morehouse
|
||||
|
||||
# macaroons
|
||||
/macaroons/ @guggero
|
||||
|
||||
# mobile
|
||||
/mobile/ @guggero
|
||||
|
||||
# monitoring
|
||||
/monitoring/ @guggero
|
||||
|
||||
# multimutex
|
||||
/multimutex/ @Roasbeef
|
||||
|
||||
# nat
|
||||
/nat/ @Roasbeef
|
||||
|
||||
# network announcements
|
||||
/netann/ @ellemouton @yyforyongyu
|
||||
|
||||
# peer
|
||||
/peer/ @ProofOfKeags @Crypt-iQ @morehouse
|
||||
|
||||
# peernotifier
|
||||
/peernotifier/ @yyforyongyu
|
||||
|
||||
# pool
|
||||
/pool/ @yyforyongyu
|
||||
|
||||
# queue
|
||||
/queue/ @bhandras
|
||||
|
||||
# record
|
||||
/record/ @guggero
|
||||
|
||||
# routing
|
||||
/routing/ @bitromortac @ellemouton @yyforyongyu
|
||||
|
||||
# rpcperms
|
||||
/rpcperms/ @guggero
|
||||
|
||||
# shachain
|
||||
/shachain/ @Roasbeef
|
||||
|
||||
# signal
|
||||
/signal/ @Roasbeef
|
||||
|
||||
# sqldb
|
||||
/sqldb/ @bhandras
|
||||
|
||||
# sweep
|
||||
/sweep/ @yyforyongyu @ziggie1984
|
||||
|
||||
# ticker
|
||||
/ticker/ @guggero
|
||||
|
||||
# tlv
|
||||
/tlv/ @Roasbeef
|
||||
|
||||
# tor
|
||||
/tor/ @yyforyongyu
|
||||
|
||||
# walletunlocker
|
||||
/walletunlocker/ @guggero
|
||||
|
||||
# watchtower
|
||||
/watchtower/ @ellemouton
|
||||
|
||||
# zpay32
|
||||
/zpay32/ @Roasbeef @morehouse
|
||||
15
.github/actions/rebase/action.yml
vendored
15
.github/actions/rebase/action.yml
vendored
@ -1,15 +0,0 @@
|
||||
name: "Rebase on to the PR target base branch"
|
||||
description: "A reusable workflow that's used to rebase the PR code on to the target base branch."
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
|
||||
steps:
|
||||
- name: fetch and rebase on ${{ github.base_ref }}
|
||||
shell: bash
|
||||
run: |
|
||||
git remote add upstream https://github.com/${{ github.repository }}
|
||||
git fetch upstream ${{ github.base_ref }}:refs/remotes/upstream/${{ github.base_ref }}
|
||||
export GIT_COMMITTER_EMAIL="lnd-ci@example.com"
|
||||
export GIT_COMMITTER_NAME="LND CI"
|
||||
git rebase upstream/${{ github.base_ref }}
|
||||
9
.github/actions/setup-go/action.yml
vendored
9
.github/actions/setup-go/action.yml
vendored
@ -4,9 +4,6 @@ inputs:
|
||||
go-version:
|
||||
description: "The version of Golang to set up"
|
||||
required: true
|
||||
key-prefix:
|
||||
description: "A prefix to use for the cache key, to separate cache entries from other workflows"
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
@ -30,10 +27,10 @@ runs:
|
||||
~/.cache/go-build
|
||||
~/Library/Caches/go-build
|
||||
~\AppData\Local\go-build
|
||||
key: ${{ runner.os }}-go-${{ inputs.go-version }}-${{ inputs.key-prefix }}-${{ github.job }}-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ runner.os }}-go-${{ inputs.go-version }}-${{ github.job }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-${{ inputs.go-version }}-${{ inputs.key-prefix }}-${{ github.job }}-
|
||||
${{ runner.os }}-go-${{ inputs.go-version }}-${{ inputs.key-prefix }}-
|
||||
${{ runner.os }}-go-${{ inputs.go-version }}-${{ github.job }}-
|
||||
${{ runner.os }}-go-${{ inputs.go-version }}-
|
||||
|
||||
- name: set GOPATH
|
||||
shell: bash
|
||||
|
||||
5
.github/pull_request_template.md
vendored
5
.github/pull_request_template.md
vendored
@ -14,7 +14,6 @@ Steps for reviewers to follow to test the change.
|
||||
- [ ] The change obeys the [Code Documentation and Commenting](https://github.com/lightningnetwork/lnd/blob/master/docs/code_contribution_guidelines.md#CodeDocumentation) guidelines, and lines wrap at 80.
|
||||
- [ ] Commits follow the [Ideal Git Commit Structure](https://github.com/lightningnetwork/lnd/blob/master/docs/code_contribution_guidelines.md#IdealGitCommitStructure).
|
||||
- [ ] Any new logging statements use an appropriate subsystem and logging level.
|
||||
- [ ] Any new lncli commands have appropriate tags in the comments for the rpc in the proto file.
|
||||
- [ ] [There is a change description in the release notes](https://github.com/lightningnetwork/lnd/tree/master/docs/release-notes), or `[skip ci]` in the commit message for small changes.
|
||||
- [ ] [There is a change description in the release notes](https://github.com/lightningnetwork/lnd/tree/master/docs/release-notes), or `[skip ci]` in the commit message for small changes.
|
||||
|
||||
📝 Please see our [Contribution Guidelines](https://github.com/lightningnetwork/lnd/blob/master/docs/code_contribution_guidelines.md) for further guidance.
|
||||
📝 Please see our [Contribution Guidelines](https://github.com/lightningnetwork/lnd/blob/master/docs/code_contribution_guidelines.md) for further guidance.
|
||||
172
.github/workflows/main.yml
vendored
172
.github/workflows/main.yml
vendored
@ -21,9 +21,7 @@ defaults:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
BITCOIN_VERSION: "28"
|
||||
|
||||
TRANCHES: 8
|
||||
BITCOIN_VERSION: "23.0"
|
||||
|
||||
# If you change this value, please change it in the following files as well:
|
||||
# /.travis.yml
|
||||
@ -31,32 +29,9 @@ env:
|
||||
# /dev.Dockerfile
|
||||
# /make/builder.Dockerfile
|
||||
# /.github/workflows/release.yml
|
||||
GO_VERSION: 1.22.6
|
||||
GO_VERSION: 1.20.3
|
||||
|
||||
jobs:
|
||||
########################
|
||||
# SQLC code gen check
|
||||
########################
|
||||
sqlc-check:
|
||||
name: Sqlc check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: git checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: setup go ${{ env.GO_VERSION }}
|
||||
uses: ./.github/actions/setup-go
|
||||
with:
|
||||
go-version: '${{ env.GO_VERSION }}'
|
||||
|
||||
- name: docker image cache
|
||||
uses: satackey/action-docker-layer-caching@v0.0.11
|
||||
# Ignore the failure of a step and avoid terminating the job.
|
||||
continue-on-error: true
|
||||
|
||||
- name: Generate sql models
|
||||
run: make sqlc-check
|
||||
|
||||
########################
|
||||
# RPC and mobile compilation check
|
||||
########################
|
||||
@ -103,7 +78,12 @@ jobs:
|
||||
go-version: '${{ env.GO_VERSION }}'
|
||||
|
||||
- name: fetch and rebase on ${{ github.base_ref }}
|
||||
uses: ./.github/actions/rebase
|
||||
run: |
|
||||
git remote add upstream https://github.com/${{ github.repository }}
|
||||
git fetch upstream
|
||||
export GIT_COMMITTER_EMAIL="lnd-ci@example.com"
|
||||
export GIT_COMMITTER_NAME="LND CI"
|
||||
git rebase upstream/${{ github.base_ref }}
|
||||
|
||||
- name: check commits
|
||||
run: scripts/check-each-commit.sh upstream/${{ github.base_ref }}
|
||||
@ -128,12 +108,6 @@ jobs:
|
||||
- name: check code format
|
||||
run: make fmt-check
|
||||
|
||||
- name: check go modules tidiness
|
||||
run: make tidy-module-check
|
||||
|
||||
- name: lint proto files
|
||||
run: make protolint
|
||||
|
||||
- name: lint
|
||||
run: GOGC=50 make lint
|
||||
|
||||
@ -151,7 +125,6 @@ jobs:
|
||||
uses: ./.github/actions/setup-go
|
||||
with:
|
||||
go-version: '${{ env.GO_VERSION }}'
|
||||
key-prefix: cross-compile
|
||||
|
||||
- name: build release for all architectures
|
||||
run: make release
|
||||
@ -171,7 +144,7 @@ jobs:
|
||||
with:
|
||||
go-version: '${{ env.GO_VERSION }}'
|
||||
|
||||
- name: check default values in sample-lnd.conf file
|
||||
- name: check all command line flags exist in sample-lnd.conf file
|
||||
run: make sample-conf-check
|
||||
|
||||
########################
|
||||
@ -190,35 +163,17 @@ jobs:
|
||||
- unit tags="kvdb_postgres"
|
||||
- unit tags="kvdb_sqlite"
|
||||
- btcd unit-race
|
||||
- unit-module
|
||||
|
||||
steps:
|
||||
- name: git checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: fetch and rebase on ${{ github.base_ref }}
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: ./.github/actions/rebase
|
||||
|
||||
- name: git checkout fuzzing seeds
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: lightninglabs/lnd-fuzz
|
||||
path: lnd-fuzz
|
||||
|
||||
- name: rsync fuzzing seeds
|
||||
run: rsync -a --ignore-existing lnd-fuzz/ ./
|
||||
|
||||
- name: setup go ${{ env.GO_VERSION }}
|
||||
uses: ./.github/actions/setup-go
|
||||
with:
|
||||
go-version: '${{ env.GO_VERSION }}'
|
||||
key-prefix: unit-test
|
||||
|
||||
- name: install bitcoind
|
||||
run: ./scripts/install_bitcoind.sh $BITCOIN_VERSION
|
||||
run: ./scripts/install_bitcoind.sh
|
||||
|
||||
- name: run ${{ matrix.unit_type }}
|
||||
run: make ${{ matrix.unit_type }}
|
||||
@ -228,10 +183,8 @@ jobs:
|
||||
if: matrix.unit_type == 'btcd unit-cover'
|
||||
with:
|
||||
path-to-profile: coverage.txt
|
||||
flag-name: 'unit'
|
||||
parallel: true
|
||||
|
||||
|
||||
########################
|
||||
# run ubuntu integration tests
|
||||
########################
|
||||
@ -245,54 +198,35 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- name: btcd
|
||||
args: backend=btcd cover=1
|
||||
args: backend=btcd
|
||||
- name: bitcoind
|
||||
args: backend=bitcoind cover=1
|
||||
args: backend=bitcoind
|
||||
- name: bitcoind-notxindex
|
||||
args: backend="bitcoind notxindex"
|
||||
- name: bitcoind-rpcpolling
|
||||
args: backend="bitcoind rpcpolling" cover=1
|
||||
args: backend="bitcoind rpcpolling"
|
||||
- name: bitcoind-etcd
|
||||
args: backend=bitcoind dbbackend=etcd
|
||||
- name: bitcoind-postgres
|
||||
args: backend=bitcoind dbbackend=postgres
|
||||
- name: bitcoind-sqlite
|
||||
args: backend=bitcoind dbbackend=sqlite
|
||||
- name: bitcoind-postgres-nativesql
|
||||
args: backend=bitcoind dbbackend=postgres nativesql=true
|
||||
- name: bitcoind-sqlite-nativesql
|
||||
args: backend=bitcoind dbbackend=sqlite nativesql=true
|
||||
- name: neutrino
|
||||
args: backend=neutrino cover=1
|
||||
args: backend=neutrino
|
||||
steps:
|
||||
- name: git checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: fetch and rebase on ${{ github.base_ref }}
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: ./.github/actions/rebase
|
||||
|
||||
- name: setup go ${{ env.GO_VERSION }}
|
||||
uses: ./.github/actions/setup-go
|
||||
with:
|
||||
go-version: '${{ env.GO_VERSION }}'
|
||||
key-prefix: integration-test
|
||||
|
||||
- name: install bitcoind
|
||||
run: ./scripts/install_bitcoind.sh $BITCOIN_VERSION
|
||||
run: ./scripts/install_bitcoind.sh
|
||||
|
||||
- name: run ${{ matrix.name }}
|
||||
run: make itest-parallel tranches=${{ env.TRANCHES }} ${{ matrix.args }}
|
||||
|
||||
- name: Send coverage
|
||||
if: ${{ contains(matrix.args, 'cover=1') }}
|
||||
uses: shogo82148/actions-goveralls@v1
|
||||
with:
|
||||
path-to-profile: coverage.txt
|
||||
flag-name: 'itest-${{ matrix.name }}'
|
||||
parallel: true
|
||||
run: make itest-parallel ${{ matrix.args }}
|
||||
|
||||
- name: Zip log files on failure
|
||||
if: ${{ failure() }}
|
||||
@ -318,26 +252,14 @@ jobs:
|
||||
steps:
|
||||
- name: git checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: fetch and rebase on ${{ github.base_ref }}
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: ./.github/actions/rebase
|
||||
|
||||
- name: setup go ${{ env.GO_VERSION }}
|
||||
uses: ./.github/actions/setup-go
|
||||
with:
|
||||
go-version: '${{ env.GO_VERSION }}'
|
||||
key-prefix: integration-test
|
||||
|
||||
- name: run itest
|
||||
run: make itest-parallel tranches=${{ env.TRANCHES }} windows=1
|
||||
|
||||
- name: kill any remaining lnd processes
|
||||
if: ${{ failure() }}
|
||||
shell: powershell
|
||||
run: taskkill /IM lnd-itest.exe /T /F
|
||||
run: make itest-parallel windows=1
|
||||
|
||||
- name: Zip log files on failure
|
||||
if: ${{ failure() }}
|
||||
@ -352,50 +274,6 @@ jobs:
|
||||
path: logs-itest-windows.zip
|
||||
retention-days: 5
|
||||
|
||||
########################
|
||||
# run macOS integration test
|
||||
########################
|
||||
macos-integration-test:
|
||||
name: run macOS itest
|
||||
runs-on: macos-14
|
||||
if: '!contains(github.event.pull_request.labels.*.name, ''no-itest'')'
|
||||
steps:
|
||||
- name: git checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: fetch and rebase on ${{ github.base_ref }}
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: ./.github/actions/rebase
|
||||
|
||||
- name: setup go ${{ env.GO_VERSION }}
|
||||
uses: ./.github/actions/setup-go
|
||||
with:
|
||||
go-version: '${{ env.GO_VERSION }}'
|
||||
key-prefix: integration-test
|
||||
|
||||
- name: install bitcoind
|
||||
run: |
|
||||
wget https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}.0/bitcoin-${BITCOIN_VERSION}.0-arm64-apple-darwin.tar.gz
|
||||
tar zxvf bitcoin-${BITCOIN_VERSION}.0-arm64-apple-darwin.tar.gz
|
||||
mv bitcoin-${BITCOIN_VERSION}.0 /tmp/bitcoin
|
||||
|
||||
- name: run itest
|
||||
run: PATH=$PATH:/tmp/bitcoin/bin make itest-parallel tranches=${{ env.TRANCHES }} backend=bitcoind
|
||||
|
||||
- name: Zip log files on failure
|
||||
if: ${{ failure() }}
|
||||
timeout-minutes: 5 # timeout after 5 minute
|
||||
run: 7z a logs-itest-macos.zip itest/**/*.log
|
||||
|
||||
- name: Upload log files on failure
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: logs-itest-macos
|
||||
path: logs-itest-macos.zip
|
||||
retention-days: 5
|
||||
|
||||
########################
|
||||
# check pinned dependencies
|
||||
@ -408,14 +286,14 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pinned_dep:
|
||||
- google.golang.org/grpc v1.59.0
|
||||
- github.com/golang/protobuf v1.5.3
|
||||
- google.golang.org/grpc v1.41.0
|
||||
- github.com/golang/protobuf v1.5.2
|
||||
|
||||
steps:
|
||||
- name: git checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: ensure dependencies at correct version
|
||||
- name: ensure dependences at correct version
|
||||
run: if ! grep -q "${{ matrix.pinned_dep }}" go.mod; then echo dependency ${{ matrix.pinned_dep }} should not be altered ; exit 1 ; fi
|
||||
|
||||
########################
|
||||
@ -431,13 +309,3 @@ jobs:
|
||||
|
||||
- name: release notes check
|
||||
run: scripts/check-release-notes.sh
|
||||
|
||||
# Notify about the completion of all coverage collecting jobs.
|
||||
finish:
|
||||
if: ${{ always() }}
|
||||
needs: [unit-test, ubuntu-integration-test]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: shogo82148/actions-goveralls@v1
|
||||
with:
|
||||
parallel-finished: true
|
||||
|
||||
12
.github/workflows/release.yaml
vendored
12
.github/workflows/release.yaml
vendored
@ -11,11 +11,12 @@ defaults:
|
||||
|
||||
env:
|
||||
# If you change this value, please change it in the following files as well:
|
||||
# /.travis.yml
|
||||
# /Dockerfile
|
||||
# /dev.Dockerfile
|
||||
# /make/builder.Dockerfile
|
||||
# /.github/workflows/main.yml
|
||||
GO_VERSION: 1.22.6
|
||||
GO_VERSION: 1.20.3
|
||||
|
||||
jobs:
|
||||
main:
|
||||
@ -39,10 +40,11 @@ jobs:
|
||||
run: SKIP_VERSION_CHECK=1 make release tag=${{ env.RELEASE_VERSION }}
|
||||
|
||||
- name: Create Release
|
||||
uses: lightninglabs/gh-actions/action-gh-release@c7149b6a7818d1c39b36b69e727569897b6f2c5a
|
||||
uses: lightninglabs/gh-actions/action-gh-release@2021.01.25.00
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ env.RELEASE_VERSION }}
|
||||
name: lnd ${{ env.RELEASE_VERSION }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
@ -77,20 +79,20 @@ jobs:
|
||||
|
||||
## Verifying the Release Timestamp
|
||||
|
||||
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`.
|
||||
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-roasbeef-${{ env.RELEASE_VERSION }}.sig.ots -f manifest-roasbeef-${{ env.RELEASE_VERSION }}.sig
|
||||
```
|
||||
|
||||
Alternatively, [the OpenTimestamps website](https://opentimestamps.org/) can be used to verify timestamps if one doesn't have a `bitcoind` instance accessible locally.
|
||||
Alternatively, [the open timestamps website](https://opentimestamps.org/) can be used to verify timestamps if one doesn't have a `bitcoind` instance accessible locally.
|
||||
|
||||
These timestamps should give users confidence in the integrity of this release even after the key that signed the release expires.
|
||||
|
||||
## Verifying the Release Binaries
|
||||
|
||||
Our release binaries are fully reproducible. Third parties are able to verify that the release binaries were produced properly without having to trust the release manager(s). See our [reproducible builds guide](https://github.com/lightningnetwork/lnd/blob/master/docs/release.md) for how this can be achieved.
|
||||
Our release binaries are fully reproducible. Third parties are able to verify that the release binaries were produced properly without having to trust the release manager(s). See our [reproducible builds guide](https://github.com/lightningnetwork/lnd/tree/master/build/release) for how this can be achieved.
|
||||
The release binaries are compiled with `go${{ env.GO_VERSION }}`, which is required by verifiers to arrive at the same ones.
|
||||
They include the following build tags: `autopilotrpc`, `signrpc`, `walletrpc`, `chainrpc`, `invoicesrpc`, `neutrinorpc`, `routerrpc`, `watchtowerrpc`, `monitoring`, `peersrpc`, `kvdb_postrgres`, `kvdb_etcd` and `kvdb_sqlite`. Note that these are already included in the release script, so they do not need to be provided.
|
||||
|
||||
|
||||
3
.github/workflows/stats.yml
vendored
3
.github/workflows/stats.yml
vendored
@ -8,11 +8,10 @@ jobs:
|
||||
stats:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Run pull request stats
|
||||
uses: flowwer-dev/pull-request-stats@v2.11.0
|
||||
uses: flowwer-dev/pull-request-stats@v2.5.0
|
||||
with:
|
||||
period: 30 # 30 days of review stats
|
||||
charts: true
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -38,7 +38,6 @@ itest/.minerlogs
|
||||
itest/lnd-itest
|
||||
itest/btcd-itest
|
||||
itest/.logs-*
|
||||
itest/cover
|
||||
|
||||
cmd/cmd
|
||||
*.key
|
||||
@ -66,7 +65,6 @@ profile.tmp
|
||||
.DS_Store
|
||||
|
||||
.vscode
|
||||
*.code-workspace
|
||||
|
||||
# Coverage test
|
||||
coverage.txt
|
||||
|
||||
102
.golangci.yml
102
.golangci.yml
@ -1,8 +1,18 @@
|
||||
run:
|
||||
go: "1.22.6"
|
||||
# timeout for analysis
|
||||
deadline: 10m
|
||||
|
||||
# Abort after 10 minutes.
|
||||
timeout: 10m
|
||||
# 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"
|
||||
|
||||
skip-dirs:
|
||||
- channeldb/migration_01_to_11
|
||||
- channeldb/migration/lnwire21
|
||||
|
||||
build-tags:
|
||||
- autopilotrpc
|
||||
@ -20,10 +30,6 @@ run:
|
||||
- integration
|
||||
|
||||
linters-settings:
|
||||
errorlint:
|
||||
# Check for incorrect fmt.Errorf error wrapping.
|
||||
errorf: true
|
||||
|
||||
govet:
|
||||
# Don't report about shadowed variables
|
||||
check-shadowing: false
|
||||
@ -47,6 +53,7 @@ linters-settings:
|
||||
- G306 # Poor file permissions used when writing to a new file.
|
||||
|
||||
staticcheck:
|
||||
go: "1.18"
|
||||
checks: ["-SA1019"]
|
||||
|
||||
lll:
|
||||
@ -91,15 +98,6 @@ linters-settings:
|
||||
- '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:
|
||||
@ -122,16 +120,25 @@ linters:
|
||||
- gochecknoinits
|
||||
|
||||
# Deprecated linters. See https://golangci-lint.run/usage/linters/.
|
||||
- interfacer
|
||||
- golint
|
||||
- maligned
|
||||
- scopelint
|
||||
- exhaustivestruct
|
||||
- bodyclose
|
||||
- contextcheck
|
||||
- nilerr
|
||||
- noctx
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
- structcheck
|
||||
- tparallel
|
||||
- unparam
|
||||
- wastedassign
|
||||
|
||||
- ifshort
|
||||
- varcheck
|
||||
- deadcode
|
||||
- nosnakecase
|
||||
|
||||
# Disable gofumpt as it has weird behavior regarding formatting multiple
|
||||
# lines for a function which is in conflict with our contribution
|
||||
@ -170,7 +177,7 @@ linters:
|
||||
- wrapcheck
|
||||
|
||||
# Allow dynamic errors.
|
||||
- err113
|
||||
- goerr113
|
||||
|
||||
# We use ErrXXX instead.
|
||||
- errname
|
||||
@ -186,41 +193,11 @@ linters:
|
||||
# The linter is too aggressive and doesn't add much value since reviewers
|
||||
# will also catch magic numbers that make sense to extract.
|
||||
- gomnd
|
||||
- mnd
|
||||
|
||||
# Some of the tests cannot be parallelized. On the other hand, we don't
|
||||
# gain much performance with this check so we disable it for now until
|
||||
# unit tests become our CI bottleneck.
|
||||
- paralleltest
|
||||
|
||||
# New linters that we haven't had time to address yet.
|
||||
- testifylint
|
||||
- perfsprint
|
||||
- inamedparam
|
||||
- copyloopvar
|
||||
- tagalign
|
||||
- protogetter
|
||||
- revive
|
||||
- depguard
|
||||
- gosmopolitan
|
||||
- intrange
|
||||
|
||||
|
||||
issues:
|
||||
# Only show newly introduced problems.
|
||||
new-from-rev: 77c7f776d5cbf9e147edc81d65ae5ba177a684e5
|
||||
|
||||
# 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"
|
||||
|
||||
skip-dirs:
|
||||
- channeldb/migration_01_to_11
|
||||
- channeldb/migration/lnwire21
|
||||
new-from-rev: 8c66353e4c02329abdacb5a8df29998035ec2e24
|
||||
|
||||
exclude-rules:
|
||||
# Exclude gosec from running for tests so that tests with weak randomness
|
||||
@ -229,18 +206,6 @@ issues:
|
||||
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:
|
||||
@ -261,8 +226,8 @@ issues:
|
||||
- forbidigo
|
||||
- godot
|
||||
|
||||
# Allow fmt.Printf() in commands.
|
||||
- path: cmd/commands/*
|
||||
# Allow fmt.Printf() in lncli.
|
||||
- path: cmd/lncli/*
|
||||
linters:
|
||||
- forbidigo
|
||||
|
||||
@ -274,16 +239,13 @@ issues:
|
||||
linters:
|
||||
- forbidigo
|
||||
|
||||
- path: itest/.*
|
||||
linters:
|
||||
- paralleltest
|
||||
|
||||
- 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
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
# The example configuration file for the protolint is located here:
|
||||
# https://github.com/yoheimuta/protolint/blob/master/_example/config/.protolint.yaml
|
||||
---
|
||||
# Lint directives.
|
||||
lint:
|
||||
# Linter rules.
|
||||
# Run `protolint list` to see all available rules.
|
||||
rules:
|
||||
# Determines whether or not to include the default set of linters.
|
||||
no_default: true
|
||||
|
||||
# Set the default to all linters. This option works the other way around as no_default does.
|
||||
# If you want to enable this option, delete the comment out below and no_default.
|
||||
# all_default: true.
|
||||
|
||||
# The specific linters to add.
|
||||
add:
|
||||
- MESSAGE_NAMES_UPPER_CAMEL_CASE
|
||||
- MAX_LINE_LENGTH
|
||||
- INDENT
|
||||
- FILE_NAMES_LOWER_SNAKE_CASE
|
||||
- IMPORTS_SORTED
|
||||
- PACKAGE_NAME_LOWER_CASE
|
||||
- ORDER
|
||||
- SERVICES_HAVE_COMMENT
|
||||
- RPCS_HAVE_COMMENT
|
||||
- PROTO3_FIELDS_AVOID_REQUIRED
|
||||
- PROTO3_GROUPS_AVOID
|
||||
- SYNTAX_CONSISTENT
|
||||
- RPC_NAMES_CASE
|
||||
- QUOTE_CONSISTENT
|
||||
|
||||
# Linter rules option.
|
||||
rules_option:
|
||||
# MAX_LINE_LENGTH rule option.
|
||||
max_line_length:
|
||||
# Enforces a maximum line length.
|
||||
max_chars: 80
|
||||
# Specifies the character count for tab characters.
|
||||
tab_chars: 2
|
||||
|
||||
# INDENT rule option.
|
||||
indent:
|
||||
# Available styles are 4(4-spaces), 2(2-spaces) or tab.
|
||||
style: 4
|
||||
# Specifies if it should stop considering and inserting new lines at the appropriate positions.
|
||||
# when the inner elements are on the same line. Default is false.
|
||||
not_insert_newline: true
|
||||
|
||||
# QUOTE_CONSISTENT rule option.
|
||||
quote_consistent:
|
||||
# Available quote are "double" or "single".
|
||||
quote: double
|
||||
|
||||
# ENUM_FIELD_NAMES_ZERO_VALUE_END_WITH rule option.
|
||||
enum_field_names_zero_value_end_with:
|
||||
suffix: INVALID
|
||||
|
||||
# SERVICE_NAMES_END_WITH rule option.
|
||||
service_names_end_with:
|
||||
text: Service
|
||||
|
||||
# REPEATED_FIELD_NAMES_PLURALIZED rule option.
|
||||
## The spec for each rules follows the implementation of https://github.com/gertd/go-pluralize.
|
||||
## Plus, you can refer to this rule's test code.
|
||||
repeated_field_names_pluralized:
|
||||
uncountable_rules:
|
||||
- paper
|
||||
irregular_rules:
|
||||
Irregular: Regular
|
||||
46
.travis.yml
Normal file
46
.travis.yml
Normal file
@ -0,0 +1,46 @@
|
||||
language: go
|
||||
cache:
|
||||
directories:
|
||||
- $GOCACHE
|
||||
- $GOPATH/pkg/mod
|
||||
- $GOPATH/src/github.com/btcsuite
|
||||
- $GOPATH/src/github.com/golang
|
||||
- $GOPATH/src/github.com/grpc-ecosystem
|
||||
- $GOPATH/src/gopkg.in/alecthomas
|
||||
- $GOPATH/src/google.golang.org
|
||||
|
||||
# Remove Travis' default flag --depth=50 from the git clone command to make sure
|
||||
# we have the whole git history, including the commit we lint against.
|
||||
git:
|
||||
depth: false
|
||||
|
||||
go:
|
||||
# If you change this value, please change it in the following files as well:
|
||||
# /Dockerfile
|
||||
# /dev.Dockerfile
|
||||
# /make/builder.Dockerfile
|
||||
# /.github/workflows/main.yml
|
||||
# /.github/workflows/release.yml
|
||||
- "1.20.3"
|
||||
|
||||
env:
|
||||
global:
|
||||
- GOCACHE=$HOME/.go-build
|
||||
- BITCOIN_VERSION="22.0"
|
||||
|
||||
sudo: required
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: Integration Test
|
||||
name: Bitcoind Integration ARM
|
||||
script:
|
||||
- bash ./scripts/install_bitcoind.sh
|
||||
- GOMEMLIMIT=1024MiB GOARM=7 GOARCH=arm GOOS=linux travis_wait 120 make itest-parallel backend=bitcoind tranches=8
|
||||
arch: arm64
|
||||
|
||||
after_failure:
|
||||
- |-
|
||||
LOG_FILES=$(find ./itest -name '*.log')
|
||||
echo "Uploading to termbin.com..." && for f in $LOG_FILES; do echo -n $f; cat $f | nc termbin.com 9999 | xargs -r0 printf ' uploaded to %s'; done
|
||||
echo "Uploading to file.io..." && tar -zcvO $LOG_FILES | curl -s -F 'file=@-;filename=logs.tar.gz' https://file.io | xargs -r0 printf 'logs.tar.gz uploaded to %s\n'
|
||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -1,7 +1,4 @@
|
||||
{
|
||||
"editor.tabSize": 8,
|
||||
"editor.rulers": [
|
||||
80
|
||||
],
|
||||
"go.buildTags": "autopilotrpc chainrpc dev invoicesrpc neutrinorpc peersrpc signrpc walletrpc watchtowerrpc"
|
||||
"editor.rulers": [80]
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
# If you change this value, please change it in the following files as well:
|
||||
# /.travis.yml
|
||||
# /dev.Dockerfile
|
||||
# /make/builder.Dockerfile
|
||||
# /.github/workflows/main.yml
|
||||
# /.github/workflows/release.yml
|
||||
FROM golang:1.22.6-alpine as builder
|
||||
FROM golang:1.20.3-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.
|
||||
|
||||
158
Makefile
158
Makefile
@ -3,8 +3,6 @@ ESCPKG := github.com\/lightningnetwork\/lnd
|
||||
MOBILE_PKG := $(PKG)/mobile
|
||||
TOOLS_DIR := tools
|
||||
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
BTCD_PKG := github.com/btcsuite/btcd
|
||||
GOACC_PKG := github.com/ory/go-acc
|
||||
GOIMPORTS_PKG := github.com/rinchsan/gosimports/cmd/gosimports
|
||||
@ -26,28 +24,9 @@ COMMIT := $(shell git describe --tags --dirty)
|
||||
COMMIT := $(subst -dirty,-fresh-btcpay,$(COMMIT))
|
||||
LDFLAGS := -ldflags "-X $(PKG)/build.Commit=$(COMMIT)"
|
||||
|
||||
# Determine the minor version of the active Go installation.
|
||||
ACTIVE_GO_VERSION := $(shell go version | sed -nre 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p')
|
||||
ACTIVE_GO_VERSION_MINOR := $(shell echo $(ACTIVE_GO_VERSION) | cut -d. -f2)
|
||||
|
||||
LOOPVARFIX :=
|
||||
ifeq ($(shell expr $(ACTIVE_GO_VERSION_MINOR) \>= 21), 1)
|
||||
LOOPVARFIX := GOEXPERIMENT=loopvar
|
||||
endif
|
||||
|
||||
LOOPVARFIX :=
|
||||
ifeq ($(shell expr $(GO_VERSION_MINOR) \>= 21), 1)
|
||||
LOOPVARFIX := GOEXPERIMENT=loopvar
|
||||
endif
|
||||
|
||||
# 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.22.6
|
||||
|
||||
GOBUILD := $(LOOPVARFIX) go build -v
|
||||
GOINSTALL := $(LOOPVARFIX) go install -v
|
||||
GOTEST := $(LOOPVARFIX) go test
|
||||
GOBUILD := go build -v
|
||||
GOINSTALL := go install -v
|
||||
GOTEST := go test
|
||||
|
||||
GOFILES_NOVENDOR = $(shell find . -type f -name '*.go' -not -path "./vendor/*" -not -name "*pb.go" -not -name "*pb.gw.go" -not -name "*.pb.json.go")
|
||||
|
||||
@ -79,12 +58,7 @@ ifneq ($(workers),)
|
||||
LINT_WORKERS = --concurrency=$(workers)
|
||||
endif
|
||||
|
||||
DOCKER_TOOLS = docker run \
|
||||
--rm \
|
||||
-v $(shell bash -c "go env GOCACHE || (mkdir -p /tmp/go-cache; echo /tmp/go-cache)"):/tmp/build/.cache \
|
||||
-v $(shell bash -c "go 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
|
||||
DOCKER_TOOLS = docker run -v $$(pwd):/build lnd-tools
|
||||
|
||||
GREEN := "\\033[0;32m"
|
||||
NC := "\\033[0m"
|
||||
@ -115,22 +89,19 @@ $(GOIMPORTS_BIN):
|
||||
# INSTALLATION
|
||||
# ============
|
||||
|
||||
#? build: Build lnd and lncli binaries, place them in project directory
|
||||
build:
|
||||
@$(call print, "Building debug lnd and lncli.")
|
||||
$(GOBUILD) -tags="$(DEV_TAGS)" -o lnd-debug $(DEV_GCFLAGS) $(DEV_LDFLAGS) $(PKG)/cmd/lnd
|
||||
$(GOBUILD) -tags="$(DEV_TAGS)" -o lncli-debug $(DEV_GCFLAGS) $(DEV_LDFLAGS) $(PKG)/cmd/lncli
|
||||
|
||||
#? build-itest: Build integration test binaries, place them in itest directory
|
||||
build-itest:
|
||||
@$(call print, "Building itest btcd and lnd.")
|
||||
CGO_ENABLED=0 $(GOBUILD) -tags="integration" -o itest/btcd-itest$(EXEC_SUFFIX) $(DEV_LDFLAGS) $(BTCD_PKG)
|
||||
CGO_ENABLED=0 $(GOBUILD) -tags="$(ITEST_TAGS)" $(ITEST_COVERAGE) -o itest/lnd-itest$(EXEC_SUFFIX) $(DEV_LDFLAGS) $(PKG)/cmd/lnd
|
||||
CGO_ENABLED=0 $(GOBUILD) -tags="$(ITEST_TAGS)" -o itest/lnd-itest$(EXEC_SUFFIX) $(DEV_LDFLAGS) $(PKG)/cmd/lnd
|
||||
|
||||
@$(call print, "Building itest binary for ${backend} backend.")
|
||||
CGO_ENABLED=0 $(GOTEST) -v ./itest -tags="$(DEV_TAGS) $(RPC_TAGS) integration $(backend)" -c -o itest/itest.test$(EXEC_SUFFIX)
|
||||
|
||||
#? build-itest-race: Build integration test binaries in race detector mode, place them in itest directory
|
||||
build-itest-race:
|
||||
@$(call print, "Building itest btcd and lnd with race detector.")
|
||||
CGO_ENABLED=0 $(GOBUILD) -tags="integration" -o itest/btcd-itest$(EXEC_SUFFIX) $(DEV_LDFLAGS) $(BTCD_PKG)
|
||||
@ -139,32 +110,16 @@ build-itest-race:
|
||||
@$(call print, "Building itest binary for ${backend} backend.")
|
||||
CGO_ENABLED=0 $(GOTEST) -v ./itest -tags="$(DEV_TAGS) $(RPC_TAGS) integration $(backend)" -c -o itest/itest.test$(EXEC_SUFFIX)
|
||||
|
||||
#? install-binaries: Build and install lnd and lncli binaries, place them in $GOPATH/bin
|
||||
install-binaries:
|
||||
install:
|
||||
@$(call print, "Installing lnd and lncli.")
|
||||
$(GOINSTALL) -tags="${tags}" -ldflags="$(RELEASE_LDFLAGS)" $(PKG)/cmd/lnd
|
||||
$(GOINSTALL) -tags="${tags}" -ldflags="$(RELEASE_LDFLAGS)" $(PKG)/cmd/lncli
|
||||
|
||||
#? manpages: generate and install man pages
|
||||
manpages:
|
||||
@$(call print, "Generating man pages lncli.1 and lnd.1.")
|
||||
./scripts/gen_man_pages.sh $(DESTDIR) $(PREFIX)
|
||||
|
||||
#? install: Build and install lnd and lncli binaries and place them in $GOPATH/bin.
|
||||
install: install-binaries
|
||||
|
||||
#? install-all: Performs all the same tasks as the install command along with generating and
|
||||
# installing the man pages for the lnd and lncli binaries. This command is useful in an
|
||||
# environment where a user has root access and so has write access to the man page directory.
|
||||
install-all: install manpages
|
||||
|
||||
#? release-install: Build and install lnd and lncli release binaries, place them in $GOPATH/bin
|
||||
release-install:
|
||||
@$(call print, "Installing release lnd and lncli.")
|
||||
env CGO_ENABLED=0 $(GOINSTALL) -v -trimpath -ldflags="$(RELEASE_LDFLAGS)" -tags="$(RELEASE_TAGS)" $(PKG)/cmd/lnd
|
||||
env CGO_ENABLED=0 $(GOINSTALL) -v -trimpath -ldflags="$(RELEASE_LDFLAGS)" -tags="$(RELEASE_TAGS)" $(PKG)/cmd/lncli
|
||||
|
||||
#? release: Build the full set of reproducible release binaries for all supported platforms
|
||||
# Make sure the generated mobile RPC stubs don't influence our vendor package
|
||||
# by removing them first in the clean-mobile target.
|
||||
release: clean-mobile
|
||||
@ -172,7 +127,6 @@ release: clean-mobile
|
||||
$(VERSION_CHECK)
|
||||
./scripts/release.sh build-release "$(VERSION_TAG)" "$(BUILD_SYSTEM)" "$(RELEASE_TAGS)" "$(RELEASE_LDFLAGS)"
|
||||
|
||||
#? docker-release: Same as release but within a docker container to support reproducible builds on BSD/MacOS platforms
|
||||
docker-release:
|
||||
@$(call print, "Building release helper docker image.")
|
||||
if [ "$(tag)" = "" ]; then echo "Must specify tag=<commit_or_tag>!"; exit 1; fi
|
||||
@ -194,7 +148,6 @@ scratch: build
|
||||
# TESTING
|
||||
# =======
|
||||
|
||||
#? check: Run unit and integration tests
|
||||
check: unit itest
|
||||
|
||||
db-instance:
|
||||
@ -212,76 +165,52 @@ ifeq ($(dbbackend),postgres)
|
||||
sleep $(POSTGRES_START_DELAY)
|
||||
endif
|
||||
|
||||
#? itest-only: Only run integration tests without re-building binaries
|
||||
itest-only: db-instance
|
||||
@$(call print, "Running integration tests with ${backend} backend.")
|
||||
rm -rf itest/*.log itest/.logs-*; date
|
||||
EXEC_SUFFIX=$(EXEC_SUFFIX) scripts/itest_part.sh 0 1 $(TEST_FLAGS) $(ITEST_FLAGS)
|
||||
$(COLLECT_ITEST_COVERAGE)
|
||||
|
||||
#? itest: Build and run integration tests
|
||||
itest: build-itest itest-only
|
||||
|
||||
#? itest-race: Build and run integration tests in race detector mode
|
||||
itest-race: build-itest-race itest-only
|
||||
|
||||
#? itest-parallel: Build and run integration tests in parallel mode, running up to ITEST_PARALLELISM test tranches in parallel (default 4)
|
||||
itest-parallel: build-itest db-instance
|
||||
@$(call print, "Running tests")
|
||||
rm -rf itest/*.log itest/.logs-*; date
|
||||
EXEC_SUFFIX=$(EXEC_SUFFIX) scripts/itest_parallel.sh $(ITEST_PARALLELISM) $(NUM_ITEST_TRANCHES) $(TEST_FLAGS) $(ITEST_FLAGS)
|
||||
$(COLLECT_ITEST_COVERAGE)
|
||||
EXEC_SUFFIX=$(EXEC_SUFFIX) echo "$$(seq 0 $$(expr $(ITEST_PARALLELISM) - 1))" | xargs -P $(ITEST_PARALLELISM) -n 1 -I {} scripts/itest_part.sh {} $(NUM_ITEST_TRANCHES) $(TEST_FLAGS) $(ITEST_FLAGS)
|
||||
|
||||
#? itest-clean: Kill all running itest processes
|
||||
itest-clean:
|
||||
@$(call print, "Cleaning old itest processes")
|
||||
killall lnd-itest || echo "no running lnd-itest process found";
|
||||
|
||||
#? unit: Run unit tests
|
||||
unit: $(BTCD_BIN)
|
||||
@$(call print, "Running unit tests.")
|
||||
$(UNIT)
|
||||
|
||||
#? unit-module: Run unit tests of all submodules
|
||||
unit-module:
|
||||
@$(call print, "Running submodule unit tests.")
|
||||
scripts/unit_test_modules.sh
|
||||
|
||||
#? unit-debug: Run unit tests with debug log output enabled
|
||||
unit-debug: $(BTCD_BIN)
|
||||
@$(call print, "Running debug unit tests.")
|
||||
$(UNIT_DEBUG)
|
||||
|
||||
#? unit-cover: Run unit tests in coverage mode
|
||||
unit-cover: $(GOACC_BIN)
|
||||
@$(call print, "Running unit coverage tests.")
|
||||
$(GOACC)
|
||||
|
||||
#? unit-race: Run unit tests in race detector mode
|
||||
unit-race:
|
||||
@$(call print, "Running unit race tests.")
|
||||
env CGO_ENABLED=1 GORACE="history_size=7 halt_on_errors=1" $(UNIT_RACE)
|
||||
|
||||
#? unit-bench: Run benchmark tests
|
||||
unit-bench: $(BTCD_BIN)
|
||||
@$(call print, "Running benchmark tests.")
|
||||
$(UNIT_BENCH)
|
||||
|
||||
# =============
|
||||
# FLAKE HUNTING
|
||||
# =============
|
||||
|
||||
#? flakehunter: Run the integration tests continuously until one fails
|
||||
flakehunter: build-itest
|
||||
@$(call print, "Flake hunting ${backend} integration tests.")
|
||||
while [ $$? -eq 0 ]; do make itest-only icase='${icase}' backend='${backend}'; done
|
||||
|
||||
#? flake-unit: Run the unit tests continuously until one fails
|
||||
flake-unit:
|
||||
@$(call print, "Flake hunting unit tests.")
|
||||
while [ $$? -eq 0 ]; do GOTRACEBACK=all $(UNIT) -count=1; done
|
||||
|
||||
#? flakehunter-parallel: Run the integration tests continuously until one fails, running up to ITEST_PARALLELISM test tranches in parallel (default 4)
|
||||
flakehunter-parallel:
|
||||
@$(call print, "Flake hunting ${backend} integration tests in parallel.")
|
||||
while [ $$? -eq 0 ]; do make itest-parallel tranches=1 parallel=${ITEST_PARALLELISM} icase='${icase}' backend='${backend}'; done
|
||||
@ -290,7 +219,6 @@ flakehunter-parallel:
|
||||
# FUZZING
|
||||
# =============
|
||||
|
||||
#? fuzz: Run the fuzzing tests
|
||||
fuzz:
|
||||
@$(call print, "Fuzzing packages '$(FUZZPKG)'.")
|
||||
scripts/fuzz.sh run "$(FUZZPKG)" "$(FUZZ_TEST_RUN_TIME)" "$(FUZZ_NUM_PROCESSES)"
|
||||
@ -299,147 +227,84 @@ fuzz:
|
||||
# UTILITIES
|
||||
# =========
|
||||
|
||||
#? fmt: Format source code and fix imports
|
||||
fmt: $(GOIMPORTS_BIN)
|
||||
@$(call print, "Fixing imports.")
|
||||
gosimports -w $(GOFILES_NOVENDOR)
|
||||
@$(call print, "Formatting source.")
|
||||
gofmt -l -w -s $(GOFILES_NOVENDOR)
|
||||
|
||||
#? fmt-check: Make sure source code is formatted and imports are correct
|
||||
fmt-check: fmt
|
||||
@$(call print, "Checking fmt results.")
|
||||
if test -n "$$(git status --porcelain)"; then echo "code not formatted correctly, please run `make fmt` again!"; git status; git diff; exit 1; fi
|
||||
|
||||
#? check-go-version-yaml: Verify that the Go version is correct in all YAML files
|
||||
check-go-version-yaml:
|
||||
@$(call print, "Checking for target Go version (v$(GO_VERSION)) in YAML files (*.yaml, *.yml)")
|
||||
./scripts/check-go-version-yaml.sh $(GO_VERSION)
|
||||
|
||||
#? check-go-version-dockerfile: Verify that the Go version is correct in all Dockerfile files
|
||||
check-go-version-dockerfile:
|
||||
@$(call print, "Checking for target Go version (v$(GO_VERSION)) in Dockerfile files (*Dockerfile)")
|
||||
./scripts/check-go-version-dockerfile.sh $(GO_VERSION)
|
||||
|
||||
#? check-go-version: Verify that the Go version is correct in all project files
|
||||
check-go-version: check-go-version-dockerfile check-go-version-yaml
|
||||
|
||||
#? lint-source: Run static code analysis
|
||||
lint-source: docker-tools
|
||||
lint: docker-tools
|
||||
@$(call print, "Linting source.")
|
||||
$(DOCKER_TOOLS) golangci-lint run -v $(LINT_WORKERS)
|
||||
|
||||
#? lint: Run static code analysis
|
||||
lint: check-go-version lint-source
|
||||
|
||||
#? protolint: Lint proto files using protolint
|
||||
protolint:
|
||||
@$(call print, "Linting proto files.")
|
||||
docker run --rm --volume "$$(pwd):/workspace" --workdir /workspace yoheimuta/protolint lint lnrpc/
|
||||
|
||||
#? tidy-module: Run `go mod` tidy for all modules
|
||||
tidy-module:
|
||||
echo "Running 'go mod tidy' for all modules"
|
||||
scripts/tidy_modules.sh
|
||||
|
||||
#? tidy-module-check: Make sure all modules are up to date
|
||||
tidy-module-check: tidy-module
|
||||
if test -n "$$(git status --porcelain)"; then echo "modules not updated, please run `make tidy-module` again!"; git status; exit 1; fi
|
||||
|
||||
#? list: List all available make targets
|
||||
list:
|
||||
@$(call print, "Listing commands:")
|
||||
@$(call print, "Listing commands.")
|
||||
@$(MAKE) -qp | \
|
||||
awk -F':' '/^[a-zA-Z0-9][^$$#\/\t=]*:([^=]|$$)/ {split($$1,A,/ /);for(i in A)print A[i]}' | \
|
||||
grep -v Makefile | \
|
||||
sort
|
||||
|
||||
#? help: List all available make targets with their descriptions
|
||||
help: Makefile
|
||||
@$(call print, "Listing commands:")
|
||||
@sed -n 's/^#?//p' $< | column -t -s ':' | sort | sed -e 's/^/ /'
|
||||
|
||||
#? sqlc: Generate sql models and queries in Go
|
||||
sqlc:
|
||||
@$(call print, "Generating sql models and queries in Go")
|
||||
./scripts/gen_sqlc_docker.sh
|
||||
|
||||
#? sqlc-check: Make sure sql models and queries are up to date
|
||||
sqlc-check: sqlc
|
||||
@$(call print, "Verifying sql code generation.")
|
||||
if test -n "$$(git status --porcelain '*.go')"; then echo "SQL models not properly generated!"; git status --porcelain '*.go'; exit 1; fi
|
||||
|
||||
#? rpc: Compile protobuf definitions and generate REST proxy stubs
|
||||
rpc:
|
||||
@$(call print, "Compiling protos.")
|
||||
cd ./lnrpc; ./gen_protos_docker.sh
|
||||
|
||||
#? rpc-format: Format protobuf definition files
|
||||
rpc-format:
|
||||
@$(call print, "Formatting protos.")
|
||||
cd ./lnrpc; find . -name "*.proto" | xargs clang-format --style=file -i
|
||||
|
||||
#? rpc-check: Make sure protobuf definitions are up to date
|
||||
rpc-check: rpc
|
||||
@$(call print, "Verifying protos.")
|
||||
cd ./lnrpc; ../scripts/check-rest-annotations.sh
|
||||
if test -n "$$(git status --porcelain)"; then echo "Protos not properly formatted or not compiled with v3.4.0"; git status; git diff; exit 1; fi
|
||||
|
||||
#? rpc-js-compile: Compile protobuf definitions and generate JSON/WASM stubs
|
||||
rpc-js-compile:
|
||||
@$(call print, "Compiling JSON/WASM stubs.")
|
||||
GOOS=js GOARCH=wasm $(GOBUILD) -tags="$(WASM_RELEASE_TAGS)" $(PKG)/lnrpc/...
|
||||
|
||||
#? sample-conf-check: Make sure default values in the sample-lnd.conf file are set correctly
|
||||
sample-conf-check:
|
||||
@$(call print, "Checking that default values in the sample-lnd.conf file are set correctly")
|
||||
scripts/check-sample-lnd-conf.sh "$(RELEASE_TAGS)"
|
||||
@$(call print, "Making sure every flag has an example in the sample-lnd.conf file")
|
||||
for flag in $$(GO_FLAGS_COMPLETION=1 go run -tags="$(RELEASE_TAGS)" $(PKG)/cmd/lnd -- | grep -v help | cut -c3-); do if ! grep -q $$flag sample-lnd.conf; then echo "Command line flag --$$flag not added to sample-lnd.conf"; exit 1; fi; done
|
||||
|
||||
#? mobile-rpc: Compile mobile RPC stubs from the protobuf definitions
|
||||
mobile-rpc:
|
||||
@$(call print, "Creating mobile RPC from protos.")
|
||||
cd ./lnrpc; COMPILE_MOBILE=1 SUBSERVER_PREFIX=1 ./gen_protos_docker.sh
|
||||
|
||||
#? vendor: Create a vendor directory with all dependencies
|
||||
vendor:
|
||||
@$(call print, "Re-creating vendor directory.")
|
||||
rm -r vendor/; go mod vendor
|
||||
|
||||
#? apple: Build mobile RPC stubs and project template for iOS and macOS
|
||||
apple: mobile-rpc
|
||||
@$(call print, "Building iOS and macOS cxframework ($(IOS_BUILD)).")
|
||||
mkdir -p $(IOS_BUILD_DIR)
|
||||
$(GOMOBILE_BIN) bind -target=ios,iossimulator,macos -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" -ldflags "$(RELEASE_LDFLAGS)" -v -o $(IOS_BUILD) $(MOBILE_PKG)
|
||||
|
||||
#? ios: Build mobile RPC stubs and project template for iOS
|
||||
ios: mobile-rpc
|
||||
@$(call print, "Building iOS cxframework ($(IOS_BUILD)).")
|
||||
mkdir -p $(IOS_BUILD_DIR)
|
||||
$(GOMOBILE_BIN) bind -target=ios,iossimulator -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" -ldflags "$(RELEASE_LDFLAGS)" -v -o $(IOS_BUILD) $(MOBILE_PKG)
|
||||
|
||||
#? macos: Build mobile RPC stubs and project template for macOS
|
||||
macos: mobile-rpc
|
||||
@$(call print, "Building macOS cxframework ($(IOS_BUILD)).")
|
||||
mkdir -p $(IOS_BUILD_DIR)
|
||||
$(GOMOBILE_BIN) bind -target=macos -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" -ldflags "$(RELEASE_LDFLAGS)" -v -o $(IOS_BUILD) $(MOBILE_PKG)
|
||||
|
||||
#? android: Build mobile RPC stubs and project template for Android
|
||||
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)" -v -o $(ANDROID_BUILD) $(MOBILE_PKG)
|
||||
|
||||
#? mobile: Build mobile RPC stubs and project templates for iOS and Android
|
||||
mobile: ios android
|
||||
|
||||
#? clean: Remove all generated files
|
||||
clean:
|
||||
@$(call print, "Cleaning source.$(NC)")
|
||||
$(RM) ./lnd-debug ./lncli-debug
|
||||
$(RM) ./lnd-itest ./lncli-itest
|
||||
$(RM) -r ./vendor .vendor-new
|
||||
|
||||
#? clean-mobile: Remove all generated mobile files
|
||||
clean-mobile:
|
||||
@$(call print, "Cleaning autogenerated mobile RPC stubs.")
|
||||
$(RM) -r mobile/build
|
||||
@ -452,7 +317,6 @@ clean-mobile:
|
||||
install \
|
||||
scratch \
|
||||
check \
|
||||
help \
|
||||
itest-only \
|
||||
itest \
|
||||
unit \
|
||||
|
||||
152
README.md
152
README.md
@ -1,89 +1,97 @@
|
||||
# BTCPayServer LND
|
||||
## Lightning Network Daemon
|
||||
|
||||
This repository is used to build LND Docker container images that are distributed with BTCPayServer by default.
|
||||
[](https://travis-ci.org/lightningnetwork/lnd)
|
||||
[](https://github.com/lightningnetwork/lnd/blob/master/LICENSE)
|
||||
[](https://web.libera.chat/#lnd)
|
||||
[](https://godoc.org/github.com/lightningnetwork/lnd)
|
||||
|
||||
Docker images are published to https://hub.docker.com/r/btcpayserver/lnd/
|
||||
<img src="logo.png">
|
||||
|
||||
Versions:
|
||||
- [0.18.3-beta](https://hub.docker.com/layers/btcpayserver/lnd/v0.18.3-beta/images/sha256-513ddd55a5af44a14e27110ee14cb28f1c7a69205bcaa2fba4e66275c1f725e5?context=repo)
|
||||
- Includes 0.28.7-beta Loop
|
||||
- [Fix for lnd unlock password \n problem](https://github.com/btcpayserver/lnd/pull/7)
|
||||
- [0.18.1-beta](https://hub.docker.com/layers/btcpayserver/lnd/v0.18.1-beta/images/sha256-5fbfa76a218ab59bf9206485f4c0c071a525f9f0906255a5672054741d043b79?context=repo)
|
||||
- Includes 0.28.5-beta Loop
|
||||
- [0.18.0-beta](https://hub.docker.com/layers/btcpayserver/lnd/v0.18.0-beta/images/sha256-e6043dddf0bdbd5c740e882447c441b37f87f2c736ebb08747a4aff5e100d9bf?context=repo)
|
||||
- Includes 0.28.2-beta Loop
|
||||
- [0.17.4-beta](https://hub.docker.com/layers/btcpayserver/lnd/v0.17.4-beta/images/sha256-b62ecff5ca71d37f9b4846f35b4d86ddc4faa3fc1dd0618ae9221d99f47708bd?context=explore)
|
||||
- Includes 0.26.6-beta Loop
|
||||
- [0.17.3-beta](https://hub.docker.com/layers/btcpayserver/lnd/v0.17.3-beta/images/sha256-141611de6c062835e9513dd1ec4155c779d7a7b55258eb1fe06e228b0835fa56?context=repo)
|
||||
- Includes 0.26.6-beta Loop
|
||||
- [0.17.2-beta](https://hub.docker.com/layers/btcpayserver/lnd/v0.17.2-beta/images/sha256-936767369b703a67daf6db6a008a3b53c15f407d29a7ad2327a0de28f5951b30?context=explore)
|
||||
- Includes 0.26.5-beta Loop
|
||||
- [0.17.1-beta](https://hub.docker.com/layers/btcpayserver/lnd/v0.17.1-beta/images/sha256-b5c106136bd33a422463c736a1db8bd3541f95ac6f277dae86ab2a01b0c3445a?context=explore)
|
||||
- Includes 0.26.5-beta Loop
|
||||
- [0.17.0-beta](https://hub.docker.com/layers/btcpayserver/lnd/v0.17.0-beta/images/sha256-58b98f983cd786bcb4d48ea8586144cafd44d58dc3018e26bfbfcf875f495368?context=explore)
|
||||
- Includes 0.26.4-beta Loop
|
||||
- [0.17.0-beta](https://hub.docker.com/layers/btcpayserver/lnd/v0.17.0-beta/images/sha256-58b98f983cd786bcb4d48ea8586144cafd44d58dc3018e26bfbfcf875f495368?context=explore)
|
||||
- Includes 0.26.4-beta Loop
|
||||
- [0.16.4-beta-1](https://hub.docker.com/layers/btcpayserver/lnd/v0.16.4-beta-1/images/sha256-9dd204b62d6c892485b3dd8a76e8f48545ceda5702c9d47329ba4bcbc535a8b4?context=explore)
|
||||
- [0.16.3-beta](https://hub.docker.com/layers/btcpayserver/lnd/v0.16.3-beta/images/sha256-9ff34769378cfca18664c7d1da3747e7ad7fb7f38a9a7b82a3d4f85e5bfef7bf?context=explore)
|
||||
- [0.16.2-beta-1](https://hub.docker.com/layers/btcpayserver/lnd/v0.16.2-beta-1/images/sha256-bfff9de84a0a4af9d643ff555125358861b70374976b970cc00d1e7fc44ed520?context=explore)
|
||||
- [0.16.1-beta](https://hub.docker.com/layers/btcpayserver/lnd/v0.16.0-beta/images/sha256-f0eb70c20691aaa2ffc34fd5bd6c284299c84e96152cda5e46882a3aa4a3c6a2?context=explore)
|
||||
- [0.16.0-beta](https://hub.docker.com/layers/btcpayserver/lnd/v0.16.0-beta/images/sha256-f0eb70c20691aaa2ffc34fd5bd6c284299c84e96152cda5e46882a3aa4a3c6a2?context=explore)
|
||||
- [0.15.4-beta](https://hub.docker.com/layers/btcpayserver/lnd/v0.15.4-beta-1/images/sha256-cadbbff93cf36146e24fa4f32170b4b9d278a2e1acfdc50470790a94506ee9c3?context=explore)
|
||||
- [Other versions are tagged](https://github.com/btcpayserver/lnd/tags), but obsoleted and not supported.
|
||||
- All LND versions prior to 0.15.4 contain a consensus bug that prevents them from properly parsing transactions with more than 500,000 witness items per input (https://github.com/btcsuite/btcd/issues/1906)
|
||||
- All LND versions prior to 0.15.2 contain a bug that prevents them from properly parsing Taproot transactions with script size over 11000 bytes (https://github.com/lightningnetwork/lnd/issues/7002)
|
||||
- LND version 0.14.0-beta shipped with check that made it incompatable with c-lightning and eclair (https://github.com/lightningnetwork/lnd/issues/5890)
|
||||
- All LND versions prior to 0.13.3 contain specification-level vulnerability (https://lists.linuxfoundation.org/pipermail/lightning-dev/2021-October/003257.html)
|
||||
- All LND versions prior to 0.7 contain critical vulnerability (https://lists.linuxfoundation.org/pipermail/lightning-dev/2019-September/002174.html)
|
||||
The Lightning Network Daemon (`lnd`) - is a complete implementation of a
|
||||
[Lightning Network](https://lightning.network) node. `lnd` has several pluggable back-end
|
||||
chain services including [`btcd`](https://github.com/btcsuite/btcd) (a
|
||||
full-node), [`bitcoind`](https://github.com/bitcoin/bitcoin), and
|
||||
[`neutrino`](https://github.com/lightninglabs/neutrino) (a new experimental light client). The project's codebase uses the
|
||||
[btcsuite](https://github.com/btcsuite/) set of Bitcoin libraries, and also
|
||||
exports a large set of isolated re-usable Lightning Network related libraries
|
||||
within it. In the current state `lnd` is capable of:
|
||||
* Creating channels.
|
||||
* Closing channels.
|
||||
* Completely managing all channel states (including the exceptional ones!).
|
||||
* Maintaining a fully authenticated+validated channel graph.
|
||||
* Performing path finding within the network, passively forwarding incoming payments.
|
||||
* Sending outgoing [onion-encrypted payments](https://github.com/lightningnetwork/lightning-onion)
|
||||
through the network.
|
||||
* Updating advertised fee schedules.
|
||||
* Automatic channel management ([`autopilot`](https://github.com/lightningnetwork/lnd/tree/master/autopilot)).
|
||||
|
||||
Each version is marked with appropriate `basedon-vX.X.X-beta` tags. We are using `basedon` prefix in order not to conflict with LND tags from source repository.
|
||||
## Lightning Network Specification Compliance
|
||||
`lnd` _fully_ conforms to the [Lightning Network specification
|
||||
(BOLTs)](https://github.com/lightningnetwork/lightning-rfc). BOLT stands for:
|
||||
Basis of Lightning Technology. The specifications are currently being drafted
|
||||
by several groups of implementers based around the world including the
|
||||
developers of `lnd`. The set of specification documents as well as our
|
||||
implementation of the specification are still a work-in-progress. With that
|
||||
said, the current status of `lnd`'s BOLT compliance is:
|
||||
|
||||
## Updating LND version in BTCPay Server
|
||||
- [X] BOLT 1: Base Protocol
|
||||
- [X] BOLT 2: Peer Protocol for Channel Management
|
||||
- [X] BOLT 3: Bitcoin Transaction and Script Formats
|
||||
- [X] BOLT 4: Onion Routing Protocol
|
||||
- [X] BOLT 5: Recommendations for On-chain Transaction Handling
|
||||
- [X] BOLT 7: P2P Node and Channel Discovery
|
||||
- [X] BOLT 8: Encrypted and Authenticated Transport
|
||||
- [X] BOLT 9: Assigned Feature Flags
|
||||
- [X] BOLT 10: DNS Bootstrap and Assisted Node Location
|
||||
- [X] BOLT 11: Invoice Protocol for Lightning Payments
|
||||
|
||||
1. **Update https://github.com/btcpayserver/lnd**
|
||||
## Developer Resources
|
||||
|
||||
a) Go to https://github.com/lightningnetwork/lnd/releases and find the commit on which we should add our resources.
|
||||
b) Checkout a new branch for that commit, usually in the format of `lnd/v0.18.3-beta`.
|
||||
c) Cherry-pick the `Adding BtcPayServer related files and resources` commit. [Example commit](https://github.com/btcpayserver/lnd/commit/ae4bb33c6a3db8b7cc01d18fdf46e600ead9bed4).
|
||||
d) Tag it with the `basedon-v*` prefix name and push it. For v0.18.1, the tag name was `basedon-v0.18.3-beta`.
|
||||
i. Before you push the tag to CircleCI to build and publish image to Docker Hub, you can test if building the image works locally.
|
||||
ii. You can do this for linuxamd64 for example by using command `docker build --pull -t local-lnd:test_version -f linuxamd64.Dockerfile .`
|
||||
e) The build process will start (it [matches on tag format](.circleci/config.yml#L11)). Here is [an example CircleCI build](https://app.circleci.com/pipelines/github/btcpayserver/lnd/202/workflows/b90b5888-c0b8-4207-860e-a63ce21077af).
|
||||
f) The resulting image will be published to Docker Hub. Example [Docker Hub image for v0.18.3](https://hub.docker.com/layers/btcpayserver/lnd/v0.18.3-beta/images/sha256-513ddd55a5af44a14e27110ee14cb28f1c7a69205bcaa2fba4e66275c1f725e5?context=repo).
|
||||
The daemon has been designed to be as developer friendly as possible in order
|
||||
to facilitate application development on top of `lnd`. Two primary RPC
|
||||
interfaces are exported: an HTTP REST API, and a [gRPC](https://grpc.io/)
|
||||
service. The exported API's are not yet stable, so be warned: they may change
|
||||
drastically in the near future.
|
||||
|
||||
Occasionally, there are problems with:
|
||||
- Versioning of base Docker images used for building Go binaries. You may need to bump that base image - [example commit](https://github.com/btcpayserver/lnd/commit/c841954c515a9d067c24987291316b093b91c2f2).
|
||||
- [Updating Loop](https://github.com/lightninglabs/loop) as part of the package, which needs to happen occasionally. [Example bump of Loop version](https://github.com/btcpayserver/lnd/commit/b3aecc7ac58280ef662e39ba99461573a30fe79a
|
||||
An automatically generated set of documentation for the RPC APIs can be found
|
||||
at [api.lightning.community](https://api.lightning.community). A set of developer
|
||||
resources including guides, articles, example applications and community resources can be found at:
|
||||
[docs.lightning.engineering](https://docs.lightning.engineering).
|
||||
|
||||
3. **Update https://github.com/btcpayserver/BTCPayServer.Lightning**
|
||||
Finally, we also have an active
|
||||
[Slack](https://lightning.engineering/slack.html) where protocol developers, application developers, testers and users gather to
|
||||
discuss various aspects of `lnd` and also Lightning in general.
|
||||
|
||||
Now we need to update the dependency in our Lightning library project. This library has tests, so we will know if something is broken.
|
||||
## Installation
|
||||
In order to build from source, please see [the installation
|
||||
instructions](docs/INSTALL.md).
|
||||
|
||||
a) Modify the `docker-compose.yml` file to reference the new LND version. [Example](https://github.com/btcpayserver/BTCPayServer.Lightning/pull/162/commits/413784ef9b2a8e7aa0496eb91f792ff0086c0ef7).
|
||||
b) Checkout a new branch for that commit, usually in the format of `feat/lnd-0.18.1`.
|
||||
c) Title the commit `Bumping LND to 0.18.1-beta`.
|
||||
d) Open a pull request and reference Docker Hub and Tag. [Example PR](https://github.com/btcpayserver/BTCPayServer.Lightning/pull/162).
|
||||
e) Once tests pass, you can merge it.
|
||||
## Docker
|
||||
To run lnd from Docker, please see the main [Docker instructions](docs/DOCKER.md)
|
||||
|
||||
4. **Update https://github.com/btcpayserver/btcpayserver**
|
||||
## IRC
|
||||
* irc.libera.chat
|
||||
* channel #lnd
|
||||
* [webchat](https://web.libera.chat/#lnd)
|
||||
|
||||
This will give access to LND to the whole dev team and allow for further testing on their dev machines if everything works as expected.
|
||||
## Safety
|
||||
|
||||
a) Modify 4 `docker-compose.yml` files in `BTCPayServer.Tests`. [Example pull request to emulate](https://github.com/btcpayserver/btcpayserver/pull/6795).
|
||||
b) When you open the PR, include the version and link to the BTCPayServer.Lightning PR.
|
||||
c) Once tests pass, you can merge it.
|
||||
When operating a mainnet `lnd` node, please refer to our [operational safety
|
||||
guidelines](docs/safety.md). It is important to note that `lnd` is still
|
||||
**beta** software and that ignoring these operational guidelines can lead to
|
||||
loss of funds.
|
||||
|
||||
5. **Update https://github.com/btcpayserver/btcpayserver-docker**
|
||||
## Security
|
||||
|
||||
a) Now that everything is prepared, open a PR in the btcpayserver-docker repository to allow these changes to propagate to everyone. [Example pull request](https://github.com/btcpayserver/btcpayserver-docker/pull/911).
|
||||
b) Open the PR in DRAFT mode and tag @NicolasDorier and @Pavlenex as reviewers. They typically handle releases, and once they test that the LND version update works on their server, they can ACK the update and merge it as part of the release process.
|
||||
The developers of `lnd` take security _very_ seriously. The disclosure of
|
||||
security vulnerabilities helps us secure the health of `lnd`, privacy of our
|
||||
users, and also the health of the Lightning Network as a whole. If you find
|
||||
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/5fa96010af201628bcfa61e9309d9b13d23d220f/security@lightning.engineering).
|
||||
|
||||
## Source repository
|
||||
|
||||
https://github.com/lightningnetwork/lnd
|
||||
|
||||
## Links
|
||||
* [BTCPayServer main repo](https://github.com/btcpayserver/btcpayserver)
|
||||
* [BTCPayServer-Docker repo](https://github.com/btcpayserver/btcpayserver-docker)
|
||||
* [BTCPayServer.Lightning](https://github.com/btcpayserver/BTCPayServer.Lightning)
|
||||
## Further reading
|
||||
* [Step-by-step send payment guide with docker](https://github.com/lightningnetwork/lnd/tree/master/docker)
|
||||
* [Contribution guide](https://github.com/lightningnetwork/lnd/blob/master/docs/code_contribution_guidelines.md)
|
||||
|
||||
@ -11,7 +11,7 @@ process. A lack of a birthday means that wallets don’t know how far
|
||||
back to look in the chain to ensure that they derive all the proper
|
||||
user addresses. Additionally, BIP39 use a very weak [KDF](https://en.wikipedia.org/wiki/Key_derivation_function). We use
|
||||
scrypt with modern parameters (n=32768, r=8, p=1). A set of benchmarks has
|
||||
been added, on my laptop I get about 100ms per attempt:
|
||||
been added, on my laptop I get about 100ms per attempt):
|
||||
|
||||
```shell
|
||||
$ go test -run=XXX -bench=.
|
||||
@ -48,13 +48,13 @@ the keys of the wallet.
|
||||
|
||||
The 2 byte timestamp is expressed in Bitcoin Days Genesis, meaning that
|
||||
the number of days since the timestamp in Bitcoin’s genesis block. This
|
||||
allows us to save space, and also avoid using a wasteful level of
|
||||
allow us to save space, and also avoid using a wasteful level of
|
||||
granularity. This can currently express time up until 2188.
|
||||
|
||||
Finally, the entropy is raw entropy that should be used to derive the
|
||||
wallet’s HD root.
|
||||
|
||||
## aezeed enciphering/deciphering
|
||||
## aezeed enciphering/deciperhing
|
||||
|
||||
Next, we’ll take the plaintext seed described above and encipher it to
|
||||
procure a final cipher text. We’ll then take this cipher text (the
|
||||
|
||||
@ -11,7 +11,7 @@ var (
|
||||
seed *CipherSeed
|
||||
)
|
||||
|
||||
// BenchmarkTomnemonic benchmarks the process of converting a cipher seed
|
||||
// BenchmarkFrommnemonic benchmarks the process of converting a cipher seed
|
||||
// (given the salt), to an enciphered mnemonic.
|
||||
func BenchmarkTomnemonic(b *testing.B) {
|
||||
scryptN = 32768
|
||||
|
||||
@ -533,17 +533,7 @@ func TestDecipherIncorrectMnemonic(t *testing.T) {
|
||||
// a checksum failure.
|
||||
swapIndex1 := 9
|
||||
swapIndex2 := 13
|
||||
|
||||
mnemonic[swapIndex1], mnemonic[swapIndex2] =
|
||||
mnemonic[swapIndex2], mnemonic[swapIndex1]
|
||||
|
||||
// If the words happen to be the same by pure chance, we'll try again
|
||||
// with different indexes.
|
||||
if mnemonic[swapIndex1] == mnemonic[swapIndex2] {
|
||||
swapIndex1 = 3
|
||||
mnemonic[swapIndex1], mnemonic[swapIndex2] =
|
||||
mnemonic[swapIndex2], mnemonic[swapIndex1]
|
||||
}
|
||||
mnemonic[swapIndex1], mnemonic[swapIndex2] = mnemonic[swapIndex2], mnemonic[swapIndex1]
|
||||
|
||||
// If we attempt to decrypt now, we should get a checksum failure.
|
||||
// If we attempt to map back to the original cipher seed now, then we
|
||||
|
||||
@ -5,22 +5,11 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
// UpdateLinkAliases is a function type for a function that locates the active
|
||||
// link that matches the given shortID and triggers an update based on the
|
||||
// latest values of the alias manager.
|
||||
type UpdateLinkAliases func(shortID lnwire.ShortChannelID) error
|
||||
|
||||
// ScidAliasMap is a map from a base short channel ID to a set of alias short
|
||||
// channel IDs.
|
||||
type ScidAliasMap map[lnwire.ShortChannelID][]lnwire.ShortChannelID
|
||||
|
||||
var (
|
||||
// aliasBucket stores aliases as keys and their base SCIDs as values.
|
||||
// This is used to populate the maps that the Manager uses. The keys
|
||||
@ -58,18 +47,17 @@ var (
|
||||
// operations.
|
||||
byteOrder = binary.BigEndian
|
||||
|
||||
// AliasStartBlockHeight is the starting block height of the alias
|
||||
// range.
|
||||
AliasStartBlockHeight uint32 = 16_000_000
|
||||
// startBlockHeight is the starting block height of the alias range.
|
||||
startingBlockHeight = 16_000_000
|
||||
|
||||
// AliasEndBlockHeight is the ending block height of the alias range.
|
||||
AliasEndBlockHeight uint32 = 16_250_000
|
||||
// endBlockHeight is the ending block height of the alias range.
|
||||
endBlockHeight = 16_250_000
|
||||
|
||||
// StartingAlias is the first alias ShortChannelID that will get
|
||||
// assigned by RequestAlias. The starting BlockHeight is chosen so that
|
||||
// legitimate SCIDs in integration tests aren't mistaken for an alias.
|
||||
StartingAlias = lnwire.ShortChannelID{
|
||||
BlockHeight: AliasStartBlockHeight,
|
||||
BlockHeight: uint32(startingBlockHeight),
|
||||
TxIndex: 0,
|
||||
TxPosition: 0,
|
||||
}
|
||||
@ -80,10 +68,6 @@ var (
|
||||
// errNoPeerAlias is returned when the peer's alias for a given
|
||||
// channel is not found.
|
||||
errNoPeerAlias = fmt.Errorf("no peer alias found")
|
||||
|
||||
// ErrAliasNotFound is returned when the alias is not found and can't
|
||||
// be mapped to a base SCID.
|
||||
ErrAliasNotFound = fmt.Errorf("alias not found")
|
||||
)
|
||||
|
||||
// Manager is a struct that handles aliases for LND. It has an underlying
|
||||
@ -93,14 +77,10 @@ var (
|
||||
type Manager struct {
|
||||
backend kvdb.Backend
|
||||
|
||||
// linkAliasUpdater is a function used by the alias manager to
|
||||
// facilitate live update of aliases in other subsystems.
|
||||
linkAliasUpdater UpdateLinkAliases
|
||||
|
||||
// baseToSet is a mapping from the "base" SCID to the set of aliases
|
||||
// for this channel. This mapping includes all channels that
|
||||
// negotiated the option-scid-alias feature bit.
|
||||
baseToSet ScidAliasMap
|
||||
baseToSet map[lnwire.ShortChannelID][]lnwire.ShortChannelID
|
||||
|
||||
// aliasToBase is a mapping that maps all aliases for a given channel
|
||||
// to its base SCID. This is only used for channels that have
|
||||
@ -118,15 +98,9 @@ type Manager struct {
|
||||
}
|
||||
|
||||
// NewManager initializes an alias Manager from the passed database backend.
|
||||
func NewManager(db kvdb.Backend, linkAliasUpdater UpdateLinkAliases) (*Manager,
|
||||
error) {
|
||||
|
||||
m := &Manager{
|
||||
backend: db,
|
||||
baseToSet: make(ScidAliasMap),
|
||||
linkAliasUpdater: linkAliasUpdater,
|
||||
}
|
||||
|
||||
func NewManager(db kvdb.Backend) (*Manager, error) {
|
||||
m := &Manager{backend: db}
|
||||
m.baseToSet = make(map[lnwire.ShortChannelID][]lnwire.ShortChannelID)
|
||||
m.aliasToBase = make(map[lnwire.ShortChannelID]lnwire.ShortChannelID)
|
||||
m.peerAlias = make(map[lnwire.ChannelID]lnwire.ShortChannelID)
|
||||
|
||||
@ -241,22 +215,12 @@ func (m *Manager) populateMaps() error {
|
||||
// AddLocalAlias adds a database mapping from the passed alias to the passed
|
||||
// base SCID. The gossip boolean marks whether or not to create a mapping
|
||||
// that the gossiper will use. It is set to false for the upgrade path where
|
||||
// the feature-bit is toggled on and there are existing channels. The linkUpdate
|
||||
// flag is used to signal whether this function should also trigger an update
|
||||
// on the htlcswitch scid alias maps.
|
||||
// the feature-bit is toggled on and there are existing channels.
|
||||
func (m *Manager) AddLocalAlias(alias, baseScid lnwire.ShortChannelID,
|
||||
gossip, linkUpdate bool) error {
|
||||
gossip bool) error {
|
||||
|
||||
// We need to lock the manager for the whole duration of this method,
|
||||
// except for the very last part where we call the link updater. In
|
||||
// order for us to safely use a defer _and_ still be able to manually
|
||||
// unlock, we use a sync.Once.
|
||||
m.Lock()
|
||||
unlockOnce := sync.Once{}
|
||||
unlock := func() {
|
||||
unlockOnce.Do(m.Unlock)
|
||||
}
|
||||
defer unlock()
|
||||
defer m.Unlock()
|
||||
|
||||
err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error {
|
||||
// If the caller does not want to allow the alias to be used
|
||||
@ -306,18 +270,6 @@ func (m *Manager) AddLocalAlias(alias, baseScid lnwire.ShortChannelID,
|
||||
m.aliasToBase[alias] = baseScid
|
||||
}
|
||||
|
||||
// We definitely need to unlock the Manager before calling the link
|
||||
// updater. If we don't, we'll deadlock. We use a sync.Once to ensure
|
||||
// that we only unlock once.
|
||||
unlock()
|
||||
|
||||
// Finally, we trigger a htlcswitch update if the flag is set, in order
|
||||
// for any future htlc that references the added alias to be properly
|
||||
// routed.
|
||||
if linkUpdate {
|
||||
return m.linkAliasUpdater(baseScid)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -388,74 +340,6 @@ func (m *Manager) DeleteSixConfs(baseScid lnwire.ShortChannelID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteLocalAlias removes a mapping from the database and the Manager's maps.
|
||||
func (m *Manager) DeleteLocalAlias(alias,
|
||||
baseScid lnwire.ShortChannelID) error {
|
||||
|
||||
// We need to lock the manager for the whole duration of this method,
|
||||
// except for the very last part where we call the link updater. In
|
||||
// order for us to safely use a defer _and_ still be able to manually
|
||||
// unlock, we use a sync.Once.
|
||||
m.Lock()
|
||||
unlockOnce := sync.Once{}
|
||||
unlock := func() {
|
||||
unlockOnce.Do(m.Unlock)
|
||||
}
|
||||
defer unlock()
|
||||
|
||||
err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error {
|
||||
aliasToBaseBucket, err := tx.CreateTopLevelBucket(aliasBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var aliasBytes [8]byte
|
||||
byteOrder.PutUint64(aliasBytes[:], alias.ToUint64())
|
||||
|
||||
// If the user attempts to delete an alias that doesn't exist,
|
||||
// we'll want to inform them about it and not just do nothing.
|
||||
if aliasToBaseBucket.Get(aliasBytes[:]) == nil {
|
||||
return ErrAliasNotFound
|
||||
}
|
||||
|
||||
return aliasToBaseBucket.Delete(aliasBytes[:])
|
||||
}, func() {})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now that the database state has been updated, we'll delete the
|
||||
// mapping from the Manager's maps.
|
||||
aliasSet, ok := m.baseToSet[baseScid]
|
||||
if !ok {
|
||||
return ErrAliasNotFound
|
||||
}
|
||||
|
||||
// We'll filter the alias set and remove the alias from it.
|
||||
aliasSet = fn.Filter(func(a lnwire.ShortChannelID) bool {
|
||||
return a.ToUint64() != alias.ToUint64()
|
||||
}, aliasSet)
|
||||
|
||||
// If the alias set is empty, we'll delete the base SCID from the
|
||||
// baseToSet map.
|
||||
if len(aliasSet) == 0 {
|
||||
delete(m.baseToSet, baseScid)
|
||||
} else {
|
||||
m.baseToSet[baseScid] = aliasSet
|
||||
}
|
||||
|
||||
// Finally, we'll delete the aliasToBase mapping from the Manager's
|
||||
// cache (but this is only set if we gossip the alias).
|
||||
delete(m.aliasToBase, alias)
|
||||
|
||||
// We definitely need to unlock the Manager before calling the link
|
||||
// updater. If we don't, we'll deadlock. We use a sync.Once to ensure
|
||||
// that we only unlock once.
|
||||
unlock()
|
||||
|
||||
return m.linkAliasUpdater(baseScid)
|
||||
}
|
||||
|
||||
// PutPeerAlias stores the peer's alias SCID once we learn of it in the
|
||||
// channel_ready message.
|
||||
func (m *Manager) PutPeerAlias(chanID lnwire.ChannelID,
|
||||
@ -508,19 +392,6 @@ func (m *Manager) GetPeerAlias(chanID lnwire.ChannelID) (lnwire.ShortChannelID,
|
||||
func (m *Manager) RequestAlias() (lnwire.ShortChannelID, error) {
|
||||
var nextAlias lnwire.ShortChannelID
|
||||
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
// haveAlias returns true if the passed alias is already assigned to a
|
||||
// channel in the baseToSet map.
|
||||
haveAlias := func(maybeNextAlias lnwire.ShortChannelID) bool {
|
||||
return fn.Any(func(aliasList []lnwire.ShortChannelID) bool {
|
||||
return fn.Any(func(alias lnwire.ShortChannelID) bool {
|
||||
return alias == maybeNextAlias
|
||||
}, aliasList)
|
||||
}, maps.Values(m.baseToSet))
|
||||
}
|
||||
|
||||
err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error {
|
||||
bucket, err := tx.CreateTopLevelBucket(aliasAllocBucket)
|
||||
if err != nil {
|
||||
@ -533,29 +404,6 @@ func (m *Manager) RequestAlias() (lnwire.ShortChannelID, error) {
|
||||
// StartingAlias to it.
|
||||
nextAlias = StartingAlias
|
||||
|
||||
// If the very first alias is already assigned, we'll
|
||||
// keep incrementing until we find an unassigned alias.
|
||||
// This is to avoid collision with custom added SCID
|
||||
// aliases that fall into the same range as the ones we
|
||||
// generate here monotonically. Those custom SCIDs are
|
||||
// stored in a different bucket, but we can just check
|
||||
// the in-memory map for simplicity.
|
||||
for {
|
||||
if !haveAlias(nextAlias) {
|
||||
break
|
||||
}
|
||||
|
||||
nextAlias = getNextScid(nextAlias)
|
||||
|
||||
// Abort if we've reached the end of the range.
|
||||
if nextAlias.BlockHeight >=
|
||||
AliasEndBlockHeight {
|
||||
|
||||
return fmt.Errorf("range for custom " +
|
||||
"aliases exhausted")
|
||||
}
|
||||
}
|
||||
|
||||
var scratch [8]byte
|
||||
byteOrder.PutUint64(scratch[:], nextAlias.ToUint64())
|
||||
return bucket.Put(lastAliasKey, scratch[:])
|
||||
@ -570,26 +418,6 @@ func (m *Manager) RequestAlias() (lnwire.ShortChannelID, error) {
|
||||
)
|
||||
nextAlias = getNextScid(lastScid)
|
||||
|
||||
// If the next alias is already assigned, we'll keep
|
||||
// incrementing until we find an unassigned alias. This is to
|
||||
// avoid collision with custom added SCID aliases that fall into
|
||||
// the same range as the ones we generate here monotonically.
|
||||
// Those custom SCIDs are stored in a different bucket, but we
|
||||
// can just check the in-memory map for simplicity.
|
||||
for {
|
||||
if !haveAlias(nextAlias) {
|
||||
break
|
||||
}
|
||||
|
||||
nextAlias = getNextScid(nextAlias)
|
||||
|
||||
// Abort if we've reached the end of the range.
|
||||
if nextAlias.BlockHeight >= AliasEndBlockHeight {
|
||||
return fmt.Errorf("range for custom " +
|
||||
"aliases exhausted")
|
||||
}
|
||||
}
|
||||
|
||||
var scratch [8]byte
|
||||
byteOrder.PutUint64(scratch[:], nextAlias.ToUint64())
|
||||
return bucket.Put(lastAliasKey, scratch[:])
|
||||
@ -605,11 +433,11 @@ func (m *Manager) RequestAlias() (lnwire.ShortChannelID, error) {
|
||||
|
||||
// ListAliases returns a carbon copy of baseToSet. This is used by the rpc
|
||||
// layer.
|
||||
func (m *Manager) ListAliases() ScidAliasMap {
|
||||
func (m *Manager) ListAliases() map[lnwire.ShortChannelID][]lnwire.ShortChannelID {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
baseCopy := make(ScidAliasMap)
|
||||
baseCopy := make(map[lnwire.ShortChannelID][]lnwire.ShortChannelID)
|
||||
|
||||
for k, v := range m.baseToSet {
|
||||
setCopy := make([]lnwire.ShortChannelID, len(v))
|
||||
@ -668,10 +496,10 @@ func getNextScid(last lnwire.ShortChannelID) lnwire.ShortChannelID {
|
||||
|
||||
// IsAlias returns true if the passed SCID is an alias. The function determines
|
||||
// this by looking at the BlockHeight. If the BlockHeight is greater than
|
||||
// AliasStartBlockHeight and less than AliasEndBlockHeight, then it is an alias
|
||||
// startingBlockHeight and less than endBlockHeight, then it is an alias
|
||||
// assigned by RequestAlias. These bounds only apply to aliases we generate.
|
||||
// Our peers are free to use any range they choose.
|
||||
func IsAlias(scid lnwire.ShortChannelID) bool {
|
||||
return scid.BlockHeight >= AliasStartBlockHeight &&
|
||||
scid.BlockHeight < AliasEndBlockHeight
|
||||
return scid.BlockHeight >= uint32(startingBlockHeight) &&
|
||||
scid.BlockHeight < uint32(endBlockHeight)
|
||||
}
|
||||
|
||||
@ -23,11 +23,7 @@ func TestAliasStorePeerAlias(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
linkUpdater := func(shortID lnwire.ShortChannelID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
aliasStore, err := NewManager(db, linkUpdater)
|
||||
aliasStore, err := NewManager(db)
|
||||
require.NoError(t, err)
|
||||
|
||||
var chanID1 [32]byte
|
||||
@ -56,11 +52,7 @@ func TestAliasStoreRequest(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
linkUpdater := func(shortID lnwire.ShortChannelID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
aliasStore, err := NewManager(db, linkUpdater)
|
||||
aliasStore, err := NewManager(db)
|
||||
require.NoError(t, err)
|
||||
|
||||
// We'll assert that the very first alias we receive is StartingAlias.
|
||||
@ -76,118 +68,6 @@ func TestAliasStoreRequest(t *testing.T) {
|
||||
require.Equal(t, nextAlias, alias2)
|
||||
}
|
||||
|
||||
// TestAliasLifecycle tests that the aliases can be created and deleted.
|
||||
func TestAliasLifecycle(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the backend database and use this to create the aliasStore.
|
||||
dbPath := filepath.Join(t.TempDir(), "testdb")
|
||||
db, err := kvdb.Create(
|
||||
kvdb.BoltBackendName, dbPath, true, kvdb.DefaultDBTimeout,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
updateChan := make(chan struct{}, 1)
|
||||
|
||||
linkUpdater := func(shortID lnwire.ShortChannelID) error {
|
||||
updateChan <- struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
aliasStore, err := NewManager(db, linkUpdater)
|
||||
require.NoError(t, err)
|
||||
|
||||
const (
|
||||
base = uint64(123123123)
|
||||
alias = uint64(456456456)
|
||||
)
|
||||
|
||||
// Parse the aliases and base to short channel ID format.
|
||||
baseScid := lnwire.NewShortChanIDFromInt(base)
|
||||
aliasScid := lnwire.NewShortChanIDFromInt(alias)
|
||||
aliasScid2 := lnwire.NewShortChanIDFromInt(alias + 1)
|
||||
|
||||
// Add the first alias.
|
||||
err = aliasStore.AddLocalAlias(aliasScid, baseScid, false, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The link updater should be called.
|
||||
<-updateChan
|
||||
|
||||
// Query the aliases and verify the results.
|
||||
aliasList := aliasStore.GetAliases(baseScid)
|
||||
require.Len(t, aliasList, 1)
|
||||
require.Contains(t, aliasList, aliasScid)
|
||||
|
||||
// Add the second alias.
|
||||
err = aliasStore.AddLocalAlias(aliasScid2, baseScid, false, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The link updater should be called.
|
||||
<-updateChan
|
||||
|
||||
// Query the aliases and verify the results.
|
||||
aliasList = aliasStore.GetAliases(baseScid)
|
||||
require.Len(t, aliasList, 2)
|
||||
require.Contains(t, aliasList, aliasScid)
|
||||
require.Contains(t, aliasList, aliasScid2)
|
||||
|
||||
// Delete the first alias.
|
||||
err = aliasStore.DeleteLocalAlias(aliasScid, baseScid)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The link updater should be called.
|
||||
<-updateChan
|
||||
|
||||
// We expect to get an error if we attempt to delete the same alias
|
||||
// again.
|
||||
err = aliasStore.DeleteLocalAlias(aliasScid, baseScid)
|
||||
require.ErrorIs(t, err, ErrAliasNotFound)
|
||||
|
||||
// The link updater should _not_ be called.
|
||||
select {
|
||||
case <-updateChan:
|
||||
t.Fatal("link alias updater should not have been called")
|
||||
default:
|
||||
}
|
||||
|
||||
// Query the aliases and verify that first one doesn't exist anymore.
|
||||
aliasList = aliasStore.GetAliases(baseScid)
|
||||
require.Len(t, aliasList, 1)
|
||||
require.Contains(t, aliasList, aliasScid2)
|
||||
require.NotContains(t, aliasList, aliasScid)
|
||||
|
||||
// Delete the second alias.
|
||||
err = aliasStore.DeleteLocalAlias(aliasScid2, baseScid)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The link updater should be called.
|
||||
<-updateChan
|
||||
|
||||
// Query the aliases and verify that none exists.
|
||||
aliasList = aliasStore.GetAliases(baseScid)
|
||||
require.Len(t, aliasList, 0)
|
||||
|
||||
// We now request an alias generated by the aliasStore. This should give
|
||||
// the first from the pre-defined list of allocated aliases.
|
||||
firstRequested, err := aliasStore.RequestAlias()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, StartingAlias, firstRequested)
|
||||
|
||||
// We now manually add the next alias from the range as a custom alias.
|
||||
secondAlias := getNextScid(firstRequested)
|
||||
err = aliasStore.AddLocalAlias(secondAlias, baseScid, false, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// When we now request another alias from the allocation list, we expect
|
||||
// the third one (tx position 2) to be returned.
|
||||
thirdRequested, err := aliasStore.RequestAlias()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, getNextScid(secondAlias), thirdRequested)
|
||||
require.EqualValues(t, 2, thirdRequested.TxPosition)
|
||||
}
|
||||
|
||||
// TestGetNextScid tests that given a current lnwire.ShortChannelID,
|
||||
// getNextScid returns the expected alias to use next.
|
||||
func TestGetNextScid(t *testing.T) {
|
||||
@ -200,7 +80,7 @@ func TestGetNextScid(t *testing.T) {
|
||||
name: "starting alias",
|
||||
current: StartingAlias,
|
||||
expected: lnwire.ShortChannelID{
|
||||
BlockHeight: AliasStartBlockHeight,
|
||||
BlockHeight: uint32(startingBlockHeight),
|
||||
TxIndex: 0,
|
||||
TxPosition: 1,
|
||||
},
|
||||
|
||||
@ -141,7 +141,7 @@ type Agent struct {
|
||||
// time.
|
||||
chanOpenFailures chan *chanOpenFailureUpdate
|
||||
|
||||
// heuristicUpdates is a channel where updates from active heuristics
|
||||
// heuristicUpdates is a channel where updates from active heurstics
|
||||
// will be sent.
|
||||
heuristicUpdates chan *heuristicUpdate
|
||||
|
||||
@ -629,7 +629,7 @@ func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32,
|
||||
nodes[nID] = struct{}{}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("unable to get graph nodes: %w", err)
|
||||
return fmt.Errorf("unable to get graph nodes: %v", err)
|
||||
}
|
||||
|
||||
// Use the heuristic to calculate a score for each node in the
|
||||
@ -639,7 +639,7 @@ func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32,
|
||||
a.cfg.Graph, totalChans, chanSize, nodes,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to calculate node scores : %w", err)
|
||||
return fmt.Errorf("unable to calculate node scores : %v", err)
|
||||
}
|
||||
|
||||
log.Debugf("Got scores for %d nodes", len(scores))
|
||||
@ -648,7 +648,7 @@ func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32,
|
||||
// to open channels to.
|
||||
scores, err = chooseN(numChans, scores)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to make weighted choice: %w",
|
||||
return fmt.Errorf("unable to make weighted choice: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
|
||||
@ -31,7 +31,7 @@ type AgentConstraints interface {
|
||||
MaxChanSize() btcutil.Amount
|
||||
}
|
||||
|
||||
// agentConstraints is an implementation of the AgentConstraints interface that
|
||||
// agenConstraints is an implementation of the AgentConstraints interface that
|
||||
// indicate the constraints the autopilot agent must adhere to when opening
|
||||
// channels.
|
||||
type agentConstraints struct {
|
||||
|
||||
@ -254,7 +254,7 @@ func respondMoreChans(t *testing.T, testCtx *testContext, resp moreChansResp) {
|
||||
}
|
||||
}
|
||||
|
||||
// respondNodeScores consumes the nodeScoresArgs element and responds to the
|
||||
// respondMoreChans consumes the nodeScoresArgs element and responds to the
|
||||
// agent with the given node scores.
|
||||
func respondNodeScores(t *testing.T, testCtx *testContext,
|
||||
resp map[NodeID]*NodeScore) {
|
||||
@ -468,7 +468,7 @@ func TestAgentChannelCloseSignal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestAgentBalanceUpdate ensures that once the agent receives an
|
||||
// TestAgentBalanceUpdateIncrease ensures that once the agent receives an
|
||||
// outside signal concerning a balance update, then it will re-query the
|
||||
// heuristic to determine its next action.
|
||||
func TestAgentBalanceUpdate(t *testing.T) {
|
||||
|
||||
@ -21,7 +21,7 @@ func TestWeightedChoiceEmptyMap(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// singleNonZero is a type used to generate float64 slices with one non-zero
|
||||
// singeNonZero is a type used to generate float64 slices with one non-zero
|
||||
// element.
|
||||
type singleNonZero []float64
|
||||
|
||||
|
||||
@ -84,7 +84,7 @@ func (c *WeightedCombAttachment) NodeScores(g ChannelGraph, chans []LocalChannel
|
||||
g, chans, chanSize, nodes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get sub score: %w",
|
||||
return nil, fmt.Errorf("unable to get sub score: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,6 @@ import (
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
@ -54,8 +53,6 @@ func ChannelGraphFromDatabase(db *channeldb.ChannelGraph) ChannelGraph {
|
||||
// channeldb.LightningNode. The wrapper method implement the autopilot.Node
|
||||
// interface.
|
||||
type dbNode struct {
|
||||
db *channeldb.ChannelGraph
|
||||
|
||||
tx kvdb.RTx
|
||||
|
||||
node *channeldb.LightningNode
|
||||
@ -70,7 +67,7 @@ var _ Node = (*dbNode)(nil)
|
||||
// will be returned in serialized compressed format.
|
||||
//
|
||||
// NOTE: Part of the autopilot.Node interface.
|
||||
func (d *dbNode) PubKey() [33]byte {
|
||||
func (d dbNode) PubKey() [33]byte {
|
||||
return d.node.PubKeyBytes
|
||||
}
|
||||
|
||||
@ -78,7 +75,7 @@ func (d *dbNode) PubKey() [33]byte {
|
||||
// peer is known to be listening on.
|
||||
//
|
||||
// NOTE: Part of the autopilot.Node interface.
|
||||
func (d *dbNode) Addrs() []net.Addr {
|
||||
func (d dbNode) Addrs() []net.Addr {
|
||||
return d.node.Addresses
|
||||
}
|
||||
|
||||
@ -88,44 +85,32 @@ func (d *dbNode) Addrs() []net.Addr {
|
||||
// describes the active channel.
|
||||
//
|
||||
// NOTE: Part of the autopilot.Node interface.
|
||||
func (d *dbNode) ForEachChannel(cb func(ChannelEdge) error) error {
|
||||
return d.db.ForEachNodeChannelTx(d.tx, d.node.PubKeyBytes,
|
||||
func(tx kvdb.RTx, ei *models.ChannelEdgeInfo, ep,
|
||||
_ *models.ChannelEdgePolicy) error {
|
||||
func (d dbNode) ForEachChannel(cb func(ChannelEdge) error) error {
|
||||
return d.node.ForEachChannel(d.tx, func(tx kvdb.RTx,
|
||||
ei *channeldb.ChannelEdgeInfo, ep, _ *channeldb.ChannelEdgePolicy) error {
|
||||
|
||||
// Skip channels for which no outgoing edge policy is
|
||||
// available.
|
||||
//
|
||||
// TODO(joostjager): Ideally the case where channels
|
||||
// have a nil policy should be supported, as autopilot
|
||||
// is not looking at the policies. For now, it is not
|
||||
// easily possible to get a reference to the other end
|
||||
// LightningNode object without retrieving the policy.
|
||||
if ep == nil {
|
||||
return nil
|
||||
}
|
||||
// Skip channels for which no outgoing edge policy is available.
|
||||
//
|
||||
// TODO(joostjager): Ideally the case where channels have a nil
|
||||
// policy should be supported, as autopilot is not looking at
|
||||
// the policies. For now, it is not easily possible to get a
|
||||
// reference to the other end LightningNode object without
|
||||
// retrieving the policy.
|
||||
if ep == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
node, err := d.db.FetchLightningNodeTx(
|
||||
tx, ep.ToNode,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
edge := ChannelEdge{
|
||||
ChanID: lnwire.NewShortChanIDFromInt(ep.ChannelID),
|
||||
Capacity: ei.Capacity,
|
||||
Peer: dbNode{
|
||||
tx: tx,
|
||||
node: ep.Node,
|
||||
},
|
||||
}
|
||||
|
||||
edge := ChannelEdge{
|
||||
ChanID: lnwire.NewShortChanIDFromInt(
|
||||
ep.ChannelID,
|
||||
),
|
||||
Capacity: ei.Capacity,
|
||||
Peer: &dbNode{
|
||||
tx: tx,
|
||||
db: d.db,
|
||||
node: node,
|
||||
},
|
||||
}
|
||||
|
||||
return cb(edge)
|
||||
})
|
||||
return cb(edge)
|
||||
})
|
||||
}
|
||||
|
||||
// ForEachNode is a higher-order function that should be called once for each
|
||||
@ -142,8 +127,7 @@ func (d *databaseChannelGraph) ForEachNode(cb func(Node) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
node := &dbNode{
|
||||
db: d.db,
|
||||
node := dbNode{
|
||||
tx: tx,
|
||||
node: n,
|
||||
}
|
||||
@ -238,7 +222,7 @@ func (d *databaseChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
|
||||
}
|
||||
|
||||
chanID := randChanID()
|
||||
edge := &models.ChannelEdgeInfo{
|
||||
edge := &channeldb.ChannelEdgeInfo{
|
||||
ChannelID: chanID.ToUint64(),
|
||||
Capacity: capacity,
|
||||
}
|
||||
@ -246,7 +230,7 @@ func (d *databaseChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
|
||||
if err := d.db.AddChannelEdge(edge); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
edgePolicy := &models.ChannelEdgePolicy{
|
||||
edgePolicy := &channeldb.ChannelEdgePolicy{
|
||||
SigBytes: testSig.Serialize(),
|
||||
ChannelID: chanID.ToUint64(),
|
||||
LastUpdate: time.Now(),
|
||||
@ -262,7 +246,7 @@ func (d *databaseChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
|
||||
if err := d.db.UpdateEdgePolicy(edgePolicy); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
edgePolicy = &models.ChannelEdgePolicy{
|
||||
edgePolicy = &channeldb.ChannelEdgePolicy{
|
||||
SigBytes: testSig.Serialize(),
|
||||
ChannelID: chanID.ToUint64(),
|
||||
LastUpdate: time.Now(),
|
||||
@ -281,16 +265,14 @@ func (d *databaseChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
|
||||
return &ChannelEdge{
|
||||
ChanID: chanID,
|
||||
Capacity: capacity,
|
||||
Peer: &dbNode{
|
||||
db: d.db,
|
||||
Peer: dbNode{
|
||||
node: vertex1,
|
||||
},
|
||||
},
|
||||
&ChannelEdge{
|
||||
ChanID: chanID,
|
||||
Capacity: capacity,
|
||||
Peer: &dbNode{
|
||||
db: d.db,
|
||||
Peer: dbNode{
|
||||
node: vertex2,
|
||||
},
|
||||
},
|
||||
|
||||
@ -6,9 +6,9 @@ import (
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/graph"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing"
|
||||
)
|
||||
|
||||
// ManagerCfg houses a set of values and methods that is passed to the Manager
|
||||
@ -36,7 +36,7 @@ type ManagerCfg struct {
|
||||
|
||||
// SubscribeTopology is used to get a subscription for topology changes
|
||||
// on the network.
|
||||
SubscribeTopology func() (*graph.TopologyClient, error)
|
||||
SubscribeTopology func() (*routing.TopologyClient, error)
|
||||
}
|
||||
|
||||
// Manager is struct that manages an autopilot agent, making it possible to
|
||||
@ -293,7 +293,7 @@ func (m *Manager) queryHeuristics(nodes map[NodeID]struct{}, localState bool) (
|
||||
HeuristicScores, error) {
|
||||
|
||||
// If we want to take the local state into action when querying the
|
||||
// heuristics, we fetch it. If not we'll just pass an empty slice to
|
||||
// heuristics, we fetch it. If not we'll just pass an emply slice to
|
||||
// the heuristic.
|
||||
var totalChans []LocalChannel
|
||||
var err error
|
||||
@ -341,7 +341,7 @@ func (m *Manager) queryHeuristics(nodes map[NodeID]struct{}, localState bool) (
|
||||
m.cfg.PilotCfg.Graph, totalChans, chanSize, nodes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get sub score: %w",
|
||||
return nil, fmt.Errorf("unable to get sub score: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ package autopilot
|
||||
// where 0 is maximally costly.
|
||||
const diameterCutoff = 0.75
|
||||
|
||||
// SimpleGraph stores a simplified adj graph of a channel graph to speed
|
||||
// SimpleGraph stores a simplifed adj graph of a channel graph to speed
|
||||
// up graph processing by eliminating all unnecessary hashing and map access.
|
||||
type SimpleGraph struct {
|
||||
// Nodes is a map from node index to NodeID.
|
||||
@ -26,7 +26,7 @@ func NewSimpleGraph(g ChannelGraph) (*SimpleGraph, error) {
|
||||
nextIndex := 0
|
||||
|
||||
// getNodeIndex returns the integer index of the passed node.
|
||||
// The returned index is then used to create a simplified adjacency list
|
||||
// The returned index is then used to create a simplifed 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 Node) int {
|
||||
|
||||
@ -66,7 +66,7 @@ func testTopCentrality(t *testing.T, graph testGraph,
|
||||
}
|
||||
}
|
||||
|
||||
// TestTopCentrality tests that we return the correct normalized centrality
|
||||
// TestTopCentrality tests that we return the correct normalized centralitiy
|
||||
// values given a non empty graph, and given our node has an increasing amount
|
||||
// of channels from 0 to N-1 simulating the whole range from non-connected to
|
||||
// fully connected.
|
||||
|
||||
@ -48,7 +48,7 @@ func LazyAdd() SchedulerOption {
|
||||
// set of Requests, executes them, and returns the error from the operation.
|
||||
type Scheduler interface {
|
||||
// Execute schedules a Request for execution with the next available
|
||||
// batch. This method blocks until the underlying closure has been
|
||||
// batch. This method blocks until the the underlying closure has been
|
||||
// run against the database. The resulting error is returned to the
|
||||
// caller.
|
||||
Execute(req *Request) error
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
// BlockCache is an lru cache for blocks.
|
||||
type BlockCache struct {
|
||||
Cache *lru.Cache[wire.InvVect, *neutrino.CacheableBlock]
|
||||
HashMutex *multimutex.Mutex[lntypes.Hash]
|
||||
HashMutex *multimutex.HashMutex
|
||||
}
|
||||
|
||||
// NewBlockCache creates a new BlockCache with the given maximum capacity.
|
||||
@ -23,7 +23,7 @@ func NewBlockCache(capacity uint64) *BlockCache {
|
||||
Cache: lru.NewCache[wire.InvVect, *neutrino.CacheableBlock](
|
||||
capacity,
|
||||
),
|
||||
HashMutex: multimutex.NewMutex[lntypes.Hash](),
|
||||
HashMutex: multimutex.NewHashMutex(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,26 +52,22 @@ func (bc *BlockCache) GetBlock(hash *chainhash.Hash,
|
||||
}
|
||||
|
||||
// Fetch the block from the chain backends.
|
||||
msgBlock, err := getBlockImpl(hash)
|
||||
block, err := getBlockImpl(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make a copy of the block so it won't escape to the heap.
|
||||
msgBlockCopy := msgBlock.Copy()
|
||||
block := btcutil.NewBlock(msgBlockCopy)
|
||||
|
||||
// Add the new block to blockCache. If the Cache is at its maximum
|
||||
// capacity then the LFU item will be evicted in favour of this new
|
||||
// block.
|
||||
_, err = bc.Cache.Put(
|
||||
*inv, &neutrino.CacheableBlock{
|
||||
Block: block,
|
||||
Block: btcutil.NewBlock(block),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return msgBlockCopy, nil
|
||||
return block, nil
|
||||
}
|
||||
|
||||
@ -2,16 +2,12 @@ package brontide
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
)
|
||||
|
||||
@ -63,77 +59,66 @@ var (
|
||||
// and completes the brontide handshake between them. If any part of the
|
||||
// handshake fails, this function will panic.
|
||||
func completeHandshake(t *testing.T, initiator, responder *Machine) {
|
||||
t.Helper()
|
||||
|
||||
if err := handshake(initiator, responder); err != nil {
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
}
|
||||
|
||||
// handshake actually completes the brontide handshake and bubbles up
|
||||
// an error to the calling function.
|
||||
func handshake(initiator, responder *Machine) error {
|
||||
// Generate ActOne and send to the responder.
|
||||
actOne, err := initiator.GenActOne()
|
||||
if err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := responder.RecvActOne(actOne); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate ActTwo and send to initiator.
|
||||
actTwo, err := responder.GenActTwo()
|
||||
if err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := initiator.RecvActTwo(actTwo); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate ActThree and send to responder.
|
||||
actThree, err := initiator.GenActThree()
|
||||
if err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := responder.RecvActThree(actThree); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
}
|
||||
return responder.RecvActThree(actThree)
|
||||
}
|
||||
|
||||
// dumpAndFail dumps the initiator and responder Machines and fails.
|
||||
func dumpAndFail(t *testing.T, initiator, responder *Machine, err error) {
|
||||
// nilAndPanic first nils the initiator and responder's Curve fields and then
|
||||
// panics.
|
||||
func nilAndPanic(t *testing.T, initiator, responder *Machine, err error) {
|
||||
t.Helper()
|
||||
|
||||
t.Fatalf("error: %v, initiator: %v, responder: %v", err,
|
||||
spew.Sdump(initiator), spew.Sdump(responder))
|
||||
}
|
||||
|
||||
// newInsecurePrivateKey returns a private key that is generated using a
|
||||
// cryptographically insecure RNG. This function should only be used for testing
|
||||
// where reproducibility is required.
|
||||
func newInsecurePrivateKey(t *testing.T,
|
||||
insecureRNG io.Reader) *btcec.PrivateKey {
|
||||
|
||||
key, err := ecdsa.GenerateKey(secp256k1.S256(), insecureRNG)
|
||||
if err != nil {
|
||||
t.Fatalf("error generating private key: %v", err)
|
||||
}
|
||||
|
||||
return secp256k1.PrivKeyFromBytes(key.D.Bytes())
|
||||
}
|
||||
|
||||
// getBrontideMachines returns two brontide machines that use pseudorandom keys
|
||||
// everywhere, generated from seed.
|
||||
func getBrontideMachines(t *testing.T, seed int64) (*Machine, *Machine) {
|
||||
rng := rand.New(rand.NewSource(seed))
|
||||
|
||||
initPriv := newInsecurePrivateKey(t, rng)
|
||||
respPriv := newInsecurePrivateKey(t, rng)
|
||||
respPub := respPriv.PubKey()
|
||||
// getBrontideMachines returns two brontide machines that use random keys
|
||||
// everywhere.
|
||||
func getBrontideMachines() (*Machine, *Machine) {
|
||||
initPriv, _ := btcec.NewPrivateKey()
|
||||
respPriv, _ := btcec.NewPrivateKey()
|
||||
respPub := (respPriv.PubKey())
|
||||
|
||||
initPrivECDH := &keychain.PrivKeyECDH{PrivKey: initPriv}
|
||||
respPrivECDH := &keychain.PrivKeyECDH{PrivKey: respPriv}
|
||||
|
||||
ephGen := EphemeralGenerator(func() (*btcec.PrivateKey, error) {
|
||||
return newInsecurePrivateKey(t, rng), nil
|
||||
})
|
||||
|
||||
initiator := NewBrontideMachine(true, initPrivECDH, respPub, ephGen)
|
||||
responder := NewBrontideMachine(false, respPrivECDH, nil, ephGen)
|
||||
initiator := NewBrontideMachine(true, initPrivECDH, respPub)
|
||||
responder := NewBrontideMachine(false, respPrivECDH, nil)
|
||||
|
||||
return initiator, responder
|
||||
}
|
||||
@ -159,14 +144,14 @@ func getStaticBrontideMachines() (*Machine, *Machine) {
|
||||
|
||||
// FuzzRandomActOne fuzz tests ActOne in the brontide handshake.
|
||||
func FuzzRandomActOne(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, seed int64, data []byte) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
// Check if data is large enough.
|
||||
if len(data) < ActOneSize {
|
||||
return
|
||||
}
|
||||
|
||||
// This will return brontide machines with random keys.
|
||||
_, responder := getBrontideMachines(t, seed)
|
||||
_, responder := getBrontideMachines()
|
||||
|
||||
// Copy data into [ActOneSize]byte.
|
||||
var actOne [ActOneSize]byte
|
||||
@ -174,31 +159,31 @@ func FuzzRandomActOne(f *testing.F) {
|
||||
|
||||
// Responder receives ActOne, should fail on the MAC check.
|
||||
if err := responder.RecvActOne(actOne); err == nil {
|
||||
dumpAndFail(t, nil, responder, nil)
|
||||
nilAndPanic(t, nil, responder, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// FuzzRandomActThree fuzz tests ActThree in the brontide handshake.
|
||||
func FuzzRandomActThree(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, seed int64, data []byte) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
// Check if data is large enough.
|
||||
if len(data) < ActThreeSize {
|
||||
return
|
||||
}
|
||||
|
||||
// This will return brontide machines with random keys.
|
||||
initiator, responder := getBrontideMachines(t, seed)
|
||||
initiator, responder := getBrontideMachines()
|
||||
|
||||
// Generate ActOne and send to the responder.
|
||||
actOne, err := initiator.GenActOne()
|
||||
if err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Receiving ActOne should succeed, so we panic on error.
|
||||
if err := responder.RecvActOne(actOne); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Generate ActTwo - this is not sent to the initiator because
|
||||
@ -207,7 +192,7 @@ func FuzzRandomActThree(f *testing.F) {
|
||||
// the appropriate state in the responder machine.
|
||||
_, err = responder.GenActTwo()
|
||||
if err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Copy data into [ActThreeSize]byte.
|
||||
@ -216,21 +201,21 @@ func FuzzRandomActThree(f *testing.F) {
|
||||
|
||||
// Responder receives ActThree, should fail on the MAC check.
|
||||
if err := responder.RecvActThree(actThree); err == nil {
|
||||
dumpAndFail(t, initiator, responder, nil)
|
||||
nilAndPanic(t, initiator, responder, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// FuzzRandomActTwo fuzz tests ActTwo in the brontide handshake.
|
||||
func FuzzRandomActTwo(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, seed int64, data []byte) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
// Check if data is large enough.
|
||||
if len(data) < ActTwoSize {
|
||||
return
|
||||
}
|
||||
|
||||
// This will return brontide machines with random keys.
|
||||
initiator, _ := getBrontideMachines(t, seed)
|
||||
initiator, _ := getBrontideMachines()
|
||||
|
||||
// Generate ActOne - this isn't sent to the responder because
|
||||
// nothing is done with the responder machine and this would
|
||||
@ -238,7 +223,7 @@ func FuzzRandomActTwo(f *testing.F) {
|
||||
// appropriate state in the initiator machine.
|
||||
_, err := initiator.GenActOne()
|
||||
if err != nil {
|
||||
dumpAndFail(t, initiator, nil, err)
|
||||
nilAndPanic(t, initiator, nil, err)
|
||||
}
|
||||
|
||||
// Copy data into [ActTwoSize]byte.
|
||||
@ -247,7 +232,7 @@ func FuzzRandomActTwo(f *testing.F) {
|
||||
|
||||
// Initiator receives ActTwo, should fail.
|
||||
if err := initiator.RecvActTwo(actTwo); err == nil {
|
||||
dumpAndFail(t, initiator, nil, nil)
|
||||
nilAndPanic(t, initiator, nil, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -255,9 +240,9 @@ func FuzzRandomActTwo(f *testing.F) {
|
||||
// FuzzRandomInitDecrypt fuzz tests decrypting arbitrary data with the
|
||||
// initiator.
|
||||
func FuzzRandomInitDecrypt(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, seed int64, data []byte) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
// This will return brontide machines with random keys.
|
||||
initiator, responder := getBrontideMachines(t, seed)
|
||||
initiator, responder := getBrontideMachines()
|
||||
|
||||
// Complete the brontide handshake.
|
||||
completeHandshake(t, initiator, responder)
|
||||
@ -268,7 +253,7 @@ func FuzzRandomInitDecrypt(f *testing.F) {
|
||||
// Decrypt the encrypted message using ReadMessage w/ initiator
|
||||
// machine.
|
||||
if _, err := initiator.ReadMessage(r); err == nil {
|
||||
dumpAndFail(t, initiator, responder, nil)
|
||||
nilAndPanic(t, initiator, responder, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -276,7 +261,7 @@ func FuzzRandomInitDecrypt(f *testing.F) {
|
||||
// FuzzRandomInitEncDec fuzz tests round-trip encryption and decryption between
|
||||
// the initiator and the responder.
|
||||
func FuzzRandomInitEncDec(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, seed int64, data []byte) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
// Ensure that length of message is not greater than max allowed
|
||||
// size.
|
||||
if len(data) > math.MaxUint16 {
|
||||
@ -284,7 +269,7 @@ func FuzzRandomInitEncDec(f *testing.F) {
|
||||
}
|
||||
|
||||
// This will return brontide machines with random keys.
|
||||
initiator, responder := getBrontideMachines(t, seed)
|
||||
initiator, responder := getBrontideMachines()
|
||||
|
||||
// Complete the brontide handshake.
|
||||
completeHandshake(t, initiator, responder)
|
||||
@ -293,25 +278,25 @@ func FuzzRandomInitEncDec(f *testing.F) {
|
||||
|
||||
// Encrypt the message using WriteMessage w/ initiator machine.
|
||||
if err := initiator.WriteMessage(data); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Flush the encrypted message w/ initiator machine.
|
||||
if _, err := initiator.Flush(&b); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Decrypt the ciphertext using ReadMessage w/ responder
|
||||
// machine.
|
||||
plaintext, err := responder.ReadMessage(&b)
|
||||
if err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Check that the decrypted message and the original message are
|
||||
// equal.
|
||||
if !bytes.Equal(data, plaintext) {
|
||||
dumpAndFail(t, initiator, responder, nil)
|
||||
nilAndPanic(t, initiator, responder, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -319,7 +304,7 @@ func FuzzRandomInitEncDec(f *testing.F) {
|
||||
// FuzzRandomInitEncrypt fuzz tests the encryption of arbitrary data with the
|
||||
// initiator.
|
||||
func FuzzRandomInitEncrypt(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, seed int64, data []byte) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
// Ensure that length of message is not greater than max allowed
|
||||
// size.
|
||||
if len(data) > math.MaxUint16 {
|
||||
@ -327,7 +312,7 @@ func FuzzRandomInitEncrypt(f *testing.F) {
|
||||
}
|
||||
|
||||
// This will return brontide machines with random keys.
|
||||
initiator, responder := getBrontideMachines(t, seed)
|
||||
initiator, responder := getBrontideMachines()
|
||||
|
||||
// Complete the brontide handshake.
|
||||
completeHandshake(t, initiator, responder)
|
||||
@ -336,12 +321,12 @@ func FuzzRandomInitEncrypt(f *testing.F) {
|
||||
|
||||
// Encrypt the message using WriteMessage w/ initiator machine.
|
||||
if err := initiator.WriteMessage(data); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Flush the encrypted message w/ initiator machine.
|
||||
if _, err := initiator.Flush(&b); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -349,9 +334,9 @@ func FuzzRandomInitEncrypt(f *testing.F) {
|
||||
// FuzzRandomRespDecrypt fuzz tests the decryption of arbitrary data with the
|
||||
// responder.
|
||||
func FuzzRandomRespDecrypt(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, seed int64, data []byte) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
// This will return brontide machines with random keys.
|
||||
initiator, responder := getBrontideMachines(t, seed)
|
||||
initiator, responder := getBrontideMachines()
|
||||
|
||||
// Complete the brontide handshake.
|
||||
completeHandshake(t, initiator, responder)
|
||||
@ -362,7 +347,7 @@ func FuzzRandomRespDecrypt(f *testing.F) {
|
||||
// Decrypt the encrypted message using ReadMessage w/ responder
|
||||
// machine.
|
||||
if _, err := responder.ReadMessage(r); err == nil {
|
||||
dumpAndFail(t, initiator, responder, nil)
|
||||
nilAndPanic(t, initiator, responder, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -370,7 +355,7 @@ func FuzzRandomRespDecrypt(f *testing.F) {
|
||||
// FuzzRandomRespEncDec fuzz tests round-trip encryption and decryption between
|
||||
// the responder and the initiator.
|
||||
func FuzzRandomRespEncDec(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, seed int64, data []byte) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
// Ensure that length of message is not greater than max allowed
|
||||
// size.
|
||||
if len(data) > math.MaxUint16 {
|
||||
@ -378,7 +363,7 @@ func FuzzRandomRespEncDec(f *testing.F) {
|
||||
}
|
||||
|
||||
// This will return brontide machines with random keys.
|
||||
initiator, responder := getBrontideMachines(t, seed)
|
||||
initiator, responder := getBrontideMachines()
|
||||
|
||||
// Complete the brontide handshake.
|
||||
completeHandshake(t, initiator, responder)
|
||||
@ -387,25 +372,25 @@ func FuzzRandomRespEncDec(f *testing.F) {
|
||||
|
||||
// Encrypt the message using WriteMessage w/ responder machine.
|
||||
if err := responder.WriteMessage(data); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Flush the encrypted message w/ responder machine.
|
||||
if _, err := responder.Flush(&b); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Decrypt the ciphertext using ReadMessage w/ initiator
|
||||
// machine.
|
||||
plaintext, err := initiator.ReadMessage(&b)
|
||||
if err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Check that the decrypted message and the original message are
|
||||
// equal.
|
||||
if !bytes.Equal(data, plaintext) {
|
||||
dumpAndFail(t, initiator, responder, nil)
|
||||
nilAndPanic(t, initiator, responder, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -413,7 +398,7 @@ func FuzzRandomRespEncDec(f *testing.F) {
|
||||
// FuzzRandomRespEncrypt fuzz tests encryption of arbitrary data with the
|
||||
// responder.
|
||||
func FuzzRandomRespEncrypt(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, seed int64, data []byte) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
// Ensure that length of message is not greater than max allowed
|
||||
// size.
|
||||
if len(data) > math.MaxUint16 {
|
||||
@ -421,7 +406,7 @@ func FuzzRandomRespEncrypt(f *testing.F) {
|
||||
}
|
||||
|
||||
// This will return brontide machines with random keys.
|
||||
initiator, responder := getBrontideMachines(t, seed)
|
||||
initiator, responder := getBrontideMachines()
|
||||
|
||||
// Complete the brontide handshake.
|
||||
completeHandshake(t, initiator, responder)
|
||||
@ -430,12 +415,12 @@ func FuzzRandomRespEncrypt(f *testing.F) {
|
||||
|
||||
// Encrypt the message using WriteMessage w/ responder machine.
|
||||
if err := responder.WriteMessage(data); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Flush the encrypted message w/ responder machine.
|
||||
if _, err := responder.Flush(&b); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -457,7 +442,7 @@ func FuzzStaticActOne(f *testing.F) {
|
||||
|
||||
// Responder receives ActOne, should fail.
|
||||
if err := responder.RecvActOne(actOne); err == nil {
|
||||
dumpAndFail(t, nil, responder, nil)
|
||||
nilAndPanic(t, nil, responder, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -476,12 +461,12 @@ func FuzzStaticActThree(f *testing.F) {
|
||||
// Generate ActOne and send to the responder.
|
||||
actOne, err := initiator.GenActOne()
|
||||
if err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Receiving ActOne should succeed, so we panic on error.
|
||||
if err := responder.RecvActOne(actOne); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Generate ActTwo - this is not sent to the initiator because
|
||||
@ -490,7 +475,7 @@ func FuzzStaticActThree(f *testing.F) {
|
||||
// the appropriate state in the responder machine.
|
||||
_, err = responder.GenActTwo()
|
||||
if err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Copy data into [ActThreeSize]byte.
|
||||
@ -499,7 +484,7 @@ func FuzzStaticActThree(f *testing.F) {
|
||||
|
||||
// Responder receives ActThree, should fail.
|
||||
if err := responder.RecvActThree(actThree); err == nil {
|
||||
dumpAndFail(t, initiator, responder, nil)
|
||||
nilAndPanic(t, initiator, responder, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -521,7 +506,7 @@ func FuzzStaticActTwo(f *testing.F) {
|
||||
// appropriate state in the initiator machine.
|
||||
_, err := initiator.GenActOne()
|
||||
if err != nil {
|
||||
dumpAndFail(t, initiator, nil, err)
|
||||
nilAndPanic(t, initiator, nil, err)
|
||||
}
|
||||
|
||||
// Copy data into [ActTwoSize]byte.
|
||||
@ -530,7 +515,7 @@ func FuzzStaticActTwo(f *testing.F) {
|
||||
|
||||
// Initiator receives ActTwo, should fail.
|
||||
if err := initiator.RecvActTwo(actTwo); err == nil {
|
||||
dumpAndFail(t, initiator, nil, nil)
|
||||
nilAndPanic(t, initiator, nil, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -551,7 +536,7 @@ func FuzzStaticInitDecrypt(f *testing.F) {
|
||||
// Decrypt the encrypted message using ReadMessage w/ initiator
|
||||
// machine.
|
||||
if _, err := initiator.ReadMessage(r); err == nil {
|
||||
dumpAndFail(t, initiator, responder, nil)
|
||||
nilAndPanic(t, initiator, responder, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -576,25 +561,25 @@ func FuzzStaticInitEncDec(f *testing.F) {
|
||||
|
||||
// Encrypt the message using WriteMessage w/ initiator machine.
|
||||
if err := initiator.WriteMessage(data); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Flush the encrypted message w/ initiator machine.
|
||||
if _, err := initiator.Flush(&b); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Decrypt the ciphertext using ReadMessage w/ responder
|
||||
// machine.
|
||||
plaintext, err := responder.ReadMessage(&b)
|
||||
if err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Check that the decrypted message and the original message are
|
||||
// equal.
|
||||
if !bytes.Equal(data, plaintext) {
|
||||
dumpAndFail(t, initiator, responder, nil)
|
||||
nilAndPanic(t, initiator, responder, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -619,12 +604,12 @@ func FuzzStaticInitEncrypt(f *testing.F) {
|
||||
|
||||
// Encrypt the message using WriteMessage w/ initiator machine.
|
||||
if err := initiator.WriteMessage(data); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Flush the encrypted message w/ initiator machine.
|
||||
if _, err := initiator.Flush(&b); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -645,7 +630,7 @@ func FuzzStaticRespDecrypt(f *testing.F) {
|
||||
// Decrypt the encrypted message using ReadMessage w/ responder
|
||||
// machine.
|
||||
if _, err := responder.ReadMessage(r); err == nil {
|
||||
dumpAndFail(t, initiator, responder, nil)
|
||||
nilAndPanic(t, initiator, responder, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -670,25 +655,25 @@ func FuzzStaticRespEncDec(f *testing.F) {
|
||||
|
||||
// Encrypt the message using WriteMessage w/ responder machine.
|
||||
if err := responder.WriteMessage(data); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Flush the encrypted message w/ responder machine.
|
||||
if _, err := responder.Flush(&b); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Decrypt the ciphertext using ReadMessage w/ initiator
|
||||
// machine.
|
||||
plaintext, err := initiator.ReadMessage(&b)
|
||||
if err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Check that the decrypted message and the original message are
|
||||
// equal.
|
||||
if !bytes.Equal(data, plaintext) {
|
||||
dumpAndFail(t, initiator, responder, nil)
|
||||
nilAndPanic(t, initiator, responder, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -713,12 +698,12 @@ func FuzzStaticRespEncrypt(f *testing.F) {
|
||||
|
||||
// Encrypt the message using WriteMessage w/ responder machine.
|
||||
if err := responder.WriteMessage(data); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
|
||||
// Flush the encrypted message w/ responder machine.
|
||||
if _, err := responder.Flush(&b); err != nil {
|
||||
dumpAndFail(t, initiator, responder, err)
|
||||
nilAndPanic(t, initiator, responder, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ func (l *Listener) listen() {
|
||||
// rejectedConnErr is a helper function that prepends the remote address of the
|
||||
// failed connection attempt to the original error message.
|
||||
func rejectedConnErr(err error, remoteAddr string) error {
|
||||
return fmt.Errorf("unable to accept connection from %v: %w", remoteAddr,
|
||||
return fmt.Errorf("unable to accept connection from %v: %v", remoteAddr,
|
||||
err)
|
||||
}
|
||||
|
||||
|
||||
@ -379,7 +379,7 @@ type Machine struct {
|
||||
// errors that cause partial writes.
|
||||
nextHeaderSend []byte
|
||||
|
||||
// nextBodySend holds a reference to the remaining body bytes to write
|
||||
// nextHeaderBody holds a reference to the remaining body bytes to write
|
||||
// out for a pending message. This allows us to tolerate timeout errors
|
||||
// that cause partial writes.
|
||||
nextBodySend []byte
|
||||
|
||||
@ -148,7 +148,7 @@ func TestConnectionCorrectness(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestConcurrentHandshakes verifies the listener's ability to not be blocked
|
||||
// TestConecurrentHandshakes verifies the listener's ability to not be blocked
|
||||
// by other pending handshakes. This is tested by opening multiple tcp
|
||||
// connections with the listener, without completing any of the brontide acts.
|
||||
// The test passes if real brontide dialer connects while the others are
|
||||
@ -274,7 +274,7 @@ func TestWriteMessageChunking(t *testing.T) {
|
||||
|
||||
bytesWritten, err := localConn.Write(largeMessage)
|
||||
if err != nil {
|
||||
errCh <- fmt.Errorf("unable to write message: %w", err)
|
||||
errCh <- fmt.Errorf("unable to write message: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -64,13 +64,13 @@ func (r *RotatingLogWriter) InitLogRotator(logFile string, maxLogFileSize int,
|
||||
logDir, _ := filepath.Split(logFile)
|
||||
err := os.MkdirAll(logDir, 0700)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create log directory: %w", err)
|
||||
return fmt.Errorf("failed to create log directory: %v", err)
|
||||
}
|
||||
r.logRotator, err = rotator.New(
|
||||
logFile, int64(maxLogFileSize*1024), false, maxLogFiles,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create file rotator: %w", err)
|
||||
return fmt.Errorf("failed to create file rotator: %v", err)
|
||||
}
|
||||
|
||||
// Run rotator as a goroutine now but make sure we catch any errors
|
||||
|
||||
@ -40,13 +40,13 @@ const (
|
||||
AppMajor uint = 0
|
||||
|
||||
// AppMinor defines the minor version of this binary.
|
||||
AppMinor uint = 18
|
||||
AppMinor uint = 16
|
||||
|
||||
// AppPatch defines the application patch for this binary.
|
||||
AppPatch uint = 4
|
||||
|
||||
// AppPreRelease MUST only contain characters from semanticAlphabet per
|
||||
// the semantic versioning spec.
|
||||
// AppPreRelease MUST only contain characters from semanticAlphabet
|
||||
// per the semantic versioning spec.
|
||||
AppPreRelease = "beta"
|
||||
)
|
||||
|
||||
|
||||
10
cert/go.mod
10
cert/go.mod
@ -1,14 +1,8 @@
|
||||
module github.com/lightningnetwork/lnd/cert
|
||||
|
||||
go 1.19
|
||||
|
||||
require github.com/stretchr/testify v1.8.2
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
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
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
github.com/stretchr/testify v1.5.1
|
||||
)
|
||||
|
||||
30
cert/go.sum
30
cert/go.sum
@ -1,32 +1,12 @@
|
||||
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=
|
||||
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/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.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.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
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/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/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=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
@ -260,7 +261,7 @@ func GenCertPair(org string, tlsExtraIPs, tlsExtraDomains []string,
|
||||
&template, &priv.PublicKey, priv,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create certificate: %w",
|
||||
return nil, nil, fmt.Errorf("failed to create certificate: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
@ -269,13 +270,13 @@ func GenCertPair(org string, tlsExtraIPs, tlsExtraDomains []string,
|
||||
certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to encode certificate: %w",
|
||||
return nil, nil, fmt.Errorf("failed to encode certificate: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
keybytes, err := x509.MarshalECPrivateKey(priv)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to encode privkey: %w",
|
||||
return nil, nil, fmt.Errorf("unable to encode privkey: %v",
|
||||
err)
|
||||
}
|
||||
keyBuf := &bytes.Buffer{}
|
||||
@ -283,7 +284,7 @@ func GenCertPair(org string, tlsExtraIPs, tlsExtraDomains []string,
|
||||
keyBuf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keybytes},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to encode private key: %w",
|
||||
return nil, nil, fmt.Errorf("failed to encode private key: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
@ -294,14 +295,14 @@ func GenCertPair(org string, tlsExtraIPs, tlsExtraDomains []string,
|
||||
func WriteCertPair(certFile, keyFile string, certBytes, keyBytes []byte) error {
|
||||
// Write cert and key files.
|
||||
if certFile != "" {
|
||||
err := os.WriteFile(certFile, certBytes, 0644)
|
||||
err := ioutil.WriteFile(certFile, certBytes, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if keyFile != "" {
|
||||
err := os.WriteFile(keyFile, keyBytes, 0600)
|
||||
err := ioutil.WriteFile(keyFile, keyBytes, 0600)
|
||||
if err != nil {
|
||||
os.Remove(certFile)
|
||||
return err
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package cert_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
@ -42,10 +42,10 @@ func TestIsOutdatedCert(t *testing.T) {
|
||||
// number of IPs and domains.
|
||||
for numIPs := 1; numIPs <= len(extraIPs); numIPs++ {
|
||||
for numDomains := 1; numDomains <= len(extraDomains); numDomains++ {
|
||||
certBytes, err := os.ReadFile(certPath)
|
||||
certBytes, err := ioutil.ReadFile(certPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
keyBytes, err := os.ReadFile(keyPath)
|
||||
keyBytes, err := ioutil.ReadFile(keyPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, parsedCert, err := cert.LoadCertFromBytes(
|
||||
@ -79,7 +79,7 @@ func TestIsOutdatedCert(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestIsOutdatedPermutation tests that the order of listed IPs or DNS names,
|
||||
// nor duplicates in the lists, matter for whether we consider the certificate
|
||||
// nor dulicates in the lists, matter for whether we consider the certificate
|
||||
// outdated.
|
||||
func TestIsOutdatedPermutation(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
@ -98,10 +98,10 @@ func TestIsOutdatedPermutation(t *testing.T) {
|
||||
err = cert.WriteCertPair(certPath, keyPath, certBytes, keyBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
certBytes, err = os.ReadFile(certPath)
|
||||
certBytes, err = ioutil.ReadFile(certPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
keyBytes, err = os.ReadFile(keyPath)
|
||||
keyBytes, err = ioutil.ReadFile(keyPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, parsedCert, err := cert.LoadCertFromBytes(certBytes, keyBytes)
|
||||
@ -171,10 +171,10 @@ func TestTLSDisableAutofill(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Read certs from disk.
|
||||
certBytes, err = os.ReadFile(certPath)
|
||||
certBytes, err = ioutil.ReadFile(certPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
keyBytes, err = os.ReadFile(keyPath)
|
||||
keyBytes, err = ioutil.ReadFile(keyPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Load the certificate.
|
||||
@ -230,10 +230,10 @@ func TestTLSConfig(t *testing.T) {
|
||||
err = cert.WriteCertPair(certPath, keyPath, certBytes, keyBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
certBytes, err = os.ReadFile(certPath)
|
||||
certBytes, err = ioutil.ReadFile(certPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
keyBytes, err = os.ReadFile(keyPath)
|
||||
keyBytes, err = ioutil.ReadFile(keyPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Load the certificate.
|
||||
|
||||
10
cert/tls.go
10
cert/tls.go
@ -3,7 +3,7 @@ package cert
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@ -31,11 +31,11 @@ var (
|
||||
func GetCertBytesFromPath(certPath, keyPath string) (certBytes,
|
||||
keyBytes []byte, err error) {
|
||||
|
||||
certBytes, err = os.ReadFile(certPath)
|
||||
certBytes, err = ioutil.ReadFile(certPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
keyBytes, err = os.ReadFile(keyPath)
|
||||
keyBytes, err = ioutil.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -57,7 +57,7 @@ func LoadCert(certPath, keyPath string) (tls.Certificate, *x509.Certificate,
|
||||
return tls.Certificate{}, nil, err
|
||||
}
|
||||
|
||||
// Now parse the PEM block of the certificate into its x509 data
|
||||
// Now parse the the PEM block of the certificate into its x509 data
|
||||
// structure so it can be examined in more detail.
|
||||
x509Cert, err := x509.ParseCertificate(certData.Certificate[0])
|
||||
if err != nil {
|
||||
@ -82,7 +82,7 @@ func LoadCertFromBytes(certBytes, keyBytes []byte) (tls.Certificate,
|
||||
return tls.Certificate{}, nil, err
|
||||
}
|
||||
|
||||
// Now parse the PEM block of the certificate into its x509 data
|
||||
// Now parse the the PEM block of the certificate into its x509 data
|
||||
// structure so it can be examined in more detail.
|
||||
x509Cert, err := x509.ParseCertificate(certData.Certificate[0])
|
||||
if err != nil {
|
||||
|
||||
@ -1,135 +0,0 @@
|
||||
package chainntnfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// BestBlockView is an interface that allows the querying of the most
|
||||
// up-to-date blockchain state with low overhead. Valid implementations of this
|
||||
// interface must track the latest chain state.
|
||||
type BestBlockView interface {
|
||||
// BestHeight gets the most recent block height known to the view.
|
||||
BestHeight() (uint32, error)
|
||||
|
||||
// BestBlockHeader gets the most recent block header known to the view.
|
||||
BestBlockHeader() (*wire.BlockHeader, error)
|
||||
}
|
||||
|
||||
// BestBlockTracker is a tiny subsystem that tracks the blockchain tip
|
||||
// and saves the most recent tip information in memory for querying. It is a
|
||||
// valid implementation of BestBlockView and additionally includes
|
||||
// methods for starting and stopping the system.
|
||||
type BestBlockTracker struct {
|
||||
notifier ChainNotifier
|
||||
blockNtfnStream *BlockEpochEvent
|
||||
current atomic.Pointer[BlockEpoch]
|
||||
mu sync.Mutex
|
||||
quit chan struct{}
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// This is a compile time check to ensure that BestBlockTracker implements
|
||||
// BestBlockView.
|
||||
var _ BestBlockView = (*BestBlockTracker)(nil)
|
||||
|
||||
// NewBestBlockTracker creates a new BestBlockTracker that isn't running yet.
|
||||
// It will not provide up to date information unless it has been started. The
|
||||
// ChainNotifier parameter must also be started prior to starting the
|
||||
// BestBlockTracker.
|
||||
func NewBestBlockTracker(chainNotifier ChainNotifier) *BestBlockTracker {
|
||||
return &BestBlockTracker{
|
||||
notifier: chainNotifier,
|
||||
blockNtfnStream: nil,
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// BestHeight gets the most recent block height known to the
|
||||
// BestBlockTracker.
|
||||
func (t *BestBlockTracker) BestHeight() (uint32, error) {
|
||||
epoch := t.current.Load()
|
||||
if epoch == nil {
|
||||
return 0, errors.New("best block height not yet known")
|
||||
}
|
||||
|
||||
return uint32(epoch.Height), nil
|
||||
}
|
||||
|
||||
// BestBlockHeader gets the most recent block header known to the
|
||||
// BestBlockTracker.
|
||||
func (t *BestBlockTracker) BestBlockHeader() (*wire.BlockHeader, error) {
|
||||
epoch := t.current.Load()
|
||||
if epoch == nil {
|
||||
return nil, errors.New("best block header not yet known")
|
||||
}
|
||||
|
||||
return epoch.BlockHeader, nil
|
||||
}
|
||||
|
||||
// updateLoop is a helper that subscribes to the underlying BlockEpochEvent
|
||||
// stream and updates the internal values to match the new BlockEpochs that
|
||||
// are discovered.
|
||||
//
|
||||
// MUST be run as a goroutine.
|
||||
func (t *BestBlockTracker) updateLoop() {
|
||||
defer t.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case epoch, ok := <-t.blockNtfnStream.Epochs:
|
||||
if !ok {
|
||||
Log.Error("dead epoch stream in " +
|
||||
"BestBlockTracker")
|
||||
|
||||
return
|
||||
}
|
||||
t.current.Store(epoch)
|
||||
case <-t.quit:
|
||||
t.current.Store(nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the BestBlockTracker. It is an error to start it if it
|
||||
// is already started.
|
||||
func (t *BestBlockTracker) Start() error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
if t.blockNtfnStream != nil {
|
||||
return fmt.Errorf("BestBlockTracker is already started")
|
||||
}
|
||||
|
||||
var err error
|
||||
t.blockNtfnStream, err = t.notifier.RegisterBlockEpochNtfn(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.wg.Add(1)
|
||||
go t.updateLoop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the BestBlockTracker. It is an error to stop it if it has
|
||||
// not been started or if it has already been stopped.
|
||||
func (t *BestBlockTracker) Stop() error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
if t.blockNtfnStream == nil {
|
||||
return fmt.Errorf("BestBlockTracker is not running")
|
||||
}
|
||||
close(t.quit)
|
||||
t.wg.Wait()
|
||||
t.blockNtfnStream.Cancel()
|
||||
t.blockNtfnStream = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1,112 +0,0 @@
|
||||
package chainntnfs_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type blockEpoch chainntnfs.BlockEpoch
|
||||
|
||||
func (blockEpoch) Generate(r *rand.Rand, size int) reflect.Value {
|
||||
var chainHash, prevBlockHash, merkleRootHash chainhash.Hash
|
||||
r.Read(chainHash[:])
|
||||
r.Read(prevBlockHash[:])
|
||||
r.Read(merkleRootHash[:])
|
||||
|
||||
return reflect.ValueOf(blockEpoch(chainntnfs.BlockEpoch{
|
||||
Hash: &chainHash,
|
||||
Height: r.Int31n(1000000),
|
||||
BlockHeader: &wire.BlockHeader{
|
||||
Version: 2,
|
||||
PrevBlock: prevBlockHash,
|
||||
MerkleRoot: merkleRootHash,
|
||||
Timestamp: time.Now(),
|
||||
Bits: r.Uint32(),
|
||||
Nonce: r.Uint32(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
// TestBestBlockTracker ensures that the most recent event pushed on the
|
||||
// underlying EpochChan is remembered by the BestBlockView functions as well
|
||||
// as testing the idempotence of the BestBlockView interface.
|
||||
func TestBestBlockTracker(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
notifier := &mock.ChainNotifier{
|
||||
SpendChan: nil,
|
||||
EpochChan: make(chan *chainntnfs.BlockEpoch),
|
||||
ConfChan: nil,
|
||||
}
|
||||
|
||||
chainNotifierI := chainntnfs.ChainNotifier(notifier)
|
||||
|
||||
tracker := chainntnfs.NewBestBlockTracker(chainNotifierI)
|
||||
require.Nil(t, tracker.Start(),
|
||||
"BestBlockTacker could not be started")
|
||||
|
||||
// we have to limit test cases because the poll interval of
|
||||
// wait.Predicate isn't tight enough to support the usual 100
|
||||
cfg := quick.Config{MaxCount: 50}
|
||||
correctness := func(epochRand blockEpoch) bool {
|
||||
epoch := chainntnfs.BlockEpoch(epochRand)
|
||||
notifier.EpochChan <- &epoch
|
||||
|
||||
// wait for new block to propagate
|
||||
err := wait.Predicate(
|
||||
func() bool {
|
||||
_, err := tracker.BestHeight()
|
||||
return err == nil
|
||||
},
|
||||
1*time.Second,
|
||||
)
|
||||
require.Nil(t, err,
|
||||
"BestBlockTracker: block propagation timeout")
|
||||
|
||||
height, _ := tracker.BestHeight()
|
||||
header, _ := tracker.BestBlockHeader()
|
||||
|
||||
return height == uint32(epoch.Height) &&
|
||||
header == epoch.BlockHeader
|
||||
}
|
||||
idempotence := func(epochRand blockEpoch) bool {
|
||||
epoch := chainntnfs.BlockEpoch(epochRand)
|
||||
notifier.EpochChan <- &epoch
|
||||
|
||||
// wait for new block to propagate
|
||||
err := wait.Predicate(
|
||||
func() bool {
|
||||
_, err := tracker.BestHeight()
|
||||
return err == nil
|
||||
},
|
||||
1*time.Second,
|
||||
)
|
||||
require.Nil(t, err,
|
||||
"ChainStateTracker: block propagation timeout")
|
||||
|
||||
height0, _ := tracker.BestHeight()
|
||||
height1, _ := tracker.BestHeight()
|
||||
header0, _ := tracker.BestBlockHeader()
|
||||
header1, _ := tracker.BestBlockHeader()
|
||||
|
||||
return height0 == height1 && header0 == header1
|
||||
}
|
||||
err := quick.Check(correctness, &cfg)
|
||||
require.Nil(t, err,
|
||||
"ChainStateTracker does not give up to date info: %v", err)
|
||||
|
||||
require.Nil(t, quick.Check(idempotence, &cfg),
|
||||
"ChainStateTracker is not idempotent")
|
||||
|
||||
require.Nil(t, tracker.Stop(), "ChainStateTracker could not be stopped")
|
||||
}
|
||||
@ -15,7 +15,6 @@ import (
|
||||
"github.com/btcsuite/btcwallet/chain"
|
||||
"github.com/lightningnetwork/lnd/blockcache"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/queue"
|
||||
)
|
||||
|
||||
@ -133,8 +132,7 @@ func (b *BitcoindNotifier) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
chainntnfs.Log.Info("bitcoind notifier shutting down...")
|
||||
defer chainntnfs.Log.Debug("bitcoind notifier shutdown complete")
|
||||
chainntnfs.Log.Info("bitcoind notifier shutting down")
|
||||
|
||||
// Shutdown the rpc client, this gracefully disconnects from bitcoind,
|
||||
// and cleans up all related resources.
|
||||
@ -151,12 +149,7 @@ func (b *BitcoindNotifier) Stop() error {
|
||||
|
||||
close(epochClient.epochChan)
|
||||
}
|
||||
|
||||
// The txNotifier is only initialized in the start method therefore we
|
||||
// need to make sure we don't access a nil pointer here.
|
||||
if b.txNotifier != nil {
|
||||
b.txNotifier.TearDown()
|
||||
}
|
||||
b.txNotifier.TearDown()
|
||||
|
||||
// Stop the mempool notifier.
|
||||
b.memNotifier.TearDown()
|
||||
@ -493,12 +486,7 @@ func (b *BitcoindNotifier) handleRelevantTx(tx *btcutil.Tx,
|
||||
// If this is a mempool spend, we'll ask the mempool notifier to hanlde
|
||||
// it.
|
||||
if mempool {
|
||||
err := b.memNotifier.ProcessRelevantSpendTx(tx)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Errorf("Unable to process transaction "+
|
||||
"%v: %v", tx.Hash(), err)
|
||||
}
|
||||
|
||||
b.memNotifier.ProcessRelevantSpendTx(tx)
|
||||
return
|
||||
}
|
||||
|
||||
@ -619,7 +607,7 @@ func (b *BitcoindNotifier) confDetailsManually(confRequest chainntnfs.ConfReques
|
||||
}
|
||||
|
||||
return &chainntnfs.TxConfirmation{
|
||||
Tx: tx.Copy(),
|
||||
Tx: tx,
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: height,
|
||||
TxIndex: uint32(txIndex),
|
||||
@ -642,7 +630,7 @@ func (b *BitcoindNotifier) handleBlockConnected(block chainntnfs.BlockEpoch) err
|
||||
// clients.
|
||||
rawBlock, err := b.GetBlock(block.Hash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get block: %w", err)
|
||||
return fmt.Errorf("unable to get block: %v", err)
|
||||
}
|
||||
utilBlock := btcutil.NewBlock(rawBlock)
|
||||
|
||||
@ -651,7 +639,7 @@ func (b *BitcoindNotifier) handleBlockConnected(block chainntnfs.BlockEpoch) err
|
||||
// us.
|
||||
err = b.txNotifier.ConnectTip(utilBlock, uint32(block.Height))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to connect tip: %w", err)
|
||||
return fmt.Errorf("unable to connect tip: %v", err)
|
||||
}
|
||||
|
||||
chainntnfs.Log.Infof("New block: height=%v, sha=%v", block.Height,
|
||||
@ -725,8 +713,7 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
||||
pkScript, b.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse script: %w",
|
||||
err)
|
||||
return nil, fmt.Errorf("unable to parse script: %v", err)
|
||||
}
|
||||
if err := b.chainConn.NotifyReceived(addrs); err != nil {
|
||||
return nil, err
|
||||
@ -798,8 +785,8 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
||||
// proceed with fallback methods.
|
||||
jsonErr, ok := err.(*btcjson.RPCError)
|
||||
if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo {
|
||||
return nil, fmt.Errorf("unable to query for txid "+
|
||||
"%v: %w", outpoint.Hash, err)
|
||||
return nil, fmt.Errorf("unable to query for txid %v: %v",
|
||||
outpoint.Hash, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -881,13 +868,11 @@ func (b *BitcoindNotifier) historicalSpendDetails(
|
||||
continue
|
||||
}
|
||||
|
||||
txCopy := tx.Copy()
|
||||
txHash := txCopy.TxHash()
|
||||
spendOutPoint := &txCopy.TxIn[inputIdx].PreviousOutPoint
|
||||
txHash := tx.TxHash()
|
||||
return &chainntnfs.SpendDetail{
|
||||
SpentOutPoint: spendOutPoint,
|
||||
SpentOutPoint: &tx.TxIn[inputIdx].PreviousOutPoint,
|
||||
SpenderTxHash: &txHash,
|
||||
SpendingTx: txCopy,
|
||||
SpendingTx: tx,
|
||||
SpenderInputIndex: inputIdx,
|
||||
SpendingHeight: int32(height),
|
||||
}, nil
|
||||
@ -1076,26 +1061,3 @@ func (b *BitcoindNotifier) CancelMempoolSpendEvent(
|
||||
|
||||
b.memNotifier.UnsubscribeEvent(sub)
|
||||
}
|
||||
|
||||
// LookupInputMempoolSpend takes an outpoint and queries the mempool to find
|
||||
// its spending tx. Returns the tx if found, otherwise fn.None.
|
||||
//
|
||||
// NOTE: part of the MempoolWatcher interface.
|
||||
func (b *BitcoindNotifier) LookupInputMempoolSpend(
|
||||
op wire.OutPoint) fn.Option[wire.MsgTx] {
|
||||
|
||||
// Find the spending txid.
|
||||
txid, found := b.chainConn.LookupInputMempoolSpend(op)
|
||||
if !found {
|
||||
return fn.None[wire.MsgTx]()
|
||||
}
|
||||
|
||||
// Query the spending tx using the id.
|
||||
tx, err := b.chainConn.GetRawTransaction(&txid)
|
||||
if err != nil {
|
||||
// TODO(yy): enable logging errors in this package.
|
||||
return fn.None[wire.MsgTx]()
|
||||
}
|
||||
|
||||
return fn.Some(*tx.MsgTx().Copy())
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ package bitcoindnotify
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -15,8 +14,6 @@ import (
|
||||
"github.com/lightningnetwork/lnd/blockcache"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/lntest/unittest"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -62,7 +59,7 @@ func setUpNotifier(t *testing.T, bitcoindConn *chain.BitcoindConn,
|
||||
t.Helper()
|
||||
|
||||
notifier := New(
|
||||
bitcoindConn, unittest.NetParams, spendHintCache,
|
||||
bitcoindConn, chainntnfs.NetParams, spendHintCache,
|
||||
confirmHintCache, blockCache,
|
||||
)
|
||||
if err := notifier.Start(); err != nil {
|
||||
@ -93,9 +90,6 @@ func syncNotifierWithMiner(t *testing.T, notifier *BitcoindNotifier,
|
||||
"height: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("miner height=%v, bitcoind height=%v", minerHeight,
|
||||
bitcoindHeight)
|
||||
|
||||
if bitcoindHeight == minerHeight {
|
||||
return uint32(bitcoindHeight)
|
||||
}
|
||||
@ -103,9 +97,7 @@ func syncNotifierWithMiner(t *testing.T, notifier *BitcoindNotifier,
|
||||
select {
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
case <-timeout:
|
||||
t.Fatalf("timed out in syncNotifierWithMiner, got "+
|
||||
"err=%v, minerHeight=%v, bitcoindHeight=%v",
|
||||
err, minerHeight, bitcoindHeight)
|
||||
t.Fatalf("timed out waiting to sync notifier")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -125,12 +117,12 @@ func TestHistoricalConfDetailsTxIndex(t *testing.T) {
|
||||
}
|
||||
|
||||
func testHistoricalConfDetailsTxIndex(t *testing.T, rpcPolling bool) {
|
||||
miner := unittest.NewMiner(
|
||||
t, unittest.NetParams, []string{"--txindex"}, true, 25,
|
||||
miner := chainntnfs.NewMiner(
|
||||
t, []string{"--txindex"}, true, 25,
|
||||
)
|
||||
|
||||
bitcoindConn := unittest.NewBitcoindBackend(
|
||||
t, unittest.NetParams, miner.P2PAddress(), true, rpcPolling,
|
||||
bitcoindConn := chainntnfs.NewBitcoindBackend(
|
||||
t, miner.P2PAddress(), true, rpcPolling,
|
||||
)
|
||||
|
||||
hintCache := initHintCache(t)
|
||||
@ -170,21 +162,18 @@ func testHistoricalConfDetailsTxIndex(t *testing.T, rpcPolling bool) {
|
||||
confReq, err := chainntnfs.NewConfRequest(txid, pkScript)
|
||||
require.NoError(t, err, "unable to create conf request")
|
||||
|
||||
// The transaction should be found in the mempool at this point. We use
|
||||
// wait here to give miner some time to propagate the tx to our node.
|
||||
err = wait.NoError(func() error {
|
||||
// The call should return no error.
|
||||
_, txStatus, err = notifier.historicalConfDetails(confReq, 0, 0)
|
||||
require.NoError(t, err)
|
||||
// The transaction should be found in the mempool at this point.
|
||||
_, txStatus, err = notifier.historicalConfDetails(confReq, 0, 0)
|
||||
require.NoError(t, err, "unable to retrieve historical conf details")
|
||||
|
||||
if txStatus != chainntnfs.TxFoundMempool {
|
||||
return fmt.Errorf("cannot the tx in mempool, status "+
|
||||
"is: %v", txStatus)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, wait.DefaultTimeout)
|
||||
require.NoError(t, err, "timeout waitinfg for historicalConfDetails")
|
||||
// Since it has yet to be included in a block, it should have been found
|
||||
// within the mempool.
|
||||
switch txStatus {
|
||||
case chainntnfs.TxFoundMempool:
|
||||
default:
|
||||
t.Fatalf("should have found the transaction within the "+
|
||||
"mempool, but did not: %v", txStatus)
|
||||
}
|
||||
|
||||
if _, err := miner.Client.Generate(1); err != nil {
|
||||
t.Fatalf("unable to generate block: %v", err)
|
||||
@ -223,10 +212,10 @@ func TestHistoricalConfDetailsNoTxIndex(t *testing.T) {
|
||||
}
|
||||
|
||||
func testHistoricalConfDetailsNoTxIndex(t *testing.T, rpcpolling bool) {
|
||||
miner := unittest.NewMiner(t, unittest.NetParams, nil, true, 25)
|
||||
miner := chainntnfs.NewMiner(t, nil, true, 25)
|
||||
|
||||
bitcoindConn := unittest.NewBitcoindBackend(
|
||||
t, unittest.NetParams, miner.P2PAddress(), false, rpcpolling,
|
||||
bitcoindConn := chainntnfs.NewBitcoindBackend(
|
||||
t, miner.P2PAddress(), false, rpcpolling,
|
||||
)
|
||||
|
||||
hintCache := initHintCache(t)
|
||||
|
||||
@ -14,10 +14,8 @@ import (
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"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"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/queue"
|
||||
)
|
||||
|
||||
@ -60,7 +58,7 @@ type BtcdNotifier struct {
|
||||
active int32 // To be used atomically.
|
||||
stopped int32 // To be used atomically.
|
||||
|
||||
chainConn *chain.RPCClient
|
||||
chainConn *rpcclient.Client
|
||||
chainParams *chaincfg.Params
|
||||
|
||||
notificationCancels chan interface{}
|
||||
@ -129,30 +127,21 @@ func New(config *rpcclient.ConnConfig, chainParams *chaincfg.Params,
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
|
||||
// Disable connecting to btcd within the rpcclient.New method. We
|
||||
// defer establishing the connection to our .Start() method.
|
||||
config.DisableConnectOnNew = true
|
||||
config.DisableAutoReconnect = false
|
||||
|
||||
ntfnCallbacks := &rpcclient.NotificationHandlers{
|
||||
OnBlockConnected: notifier.onBlockConnected,
|
||||
OnBlockDisconnected: notifier.onBlockDisconnected,
|
||||
OnRedeemingTx: notifier.onRedeemingTx,
|
||||
}
|
||||
|
||||
rpcCfg := &chain.RPCClientConfig{
|
||||
ReconnectAttempts: 20,
|
||||
Conn: config,
|
||||
Chain: chainParams,
|
||||
NotificationHandlers: ntfnCallbacks,
|
||||
}
|
||||
|
||||
chainRPC, err := chain.NewRPCClientWithConfig(rpcCfg)
|
||||
// Disable connecting to btcd within the rpcclient.New method. We
|
||||
// defer establishing the connection to our .Start() method.
|
||||
config.DisableConnectOnNew = true
|
||||
config.DisableAutoReconnect = false
|
||||
chainConn, err := rpcclient.New(config, ntfnCallbacks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
notifier.chainConn = chainRPC
|
||||
notifier.chainConn = chainConn
|
||||
|
||||
return notifier, nil
|
||||
}
|
||||
@ -180,8 +169,7 @@ func (b *BtcdNotifier) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
chainntnfs.Log.Info("btcd notifier shutting down...")
|
||||
defer chainntnfs.Log.Debug("btcd notifier shutdown complete")
|
||||
chainntnfs.Log.Info("btcd notifier shutting down")
|
||||
|
||||
// Shutdown the rpc client, this gracefully disconnects from btcd, and
|
||||
// cleans up all related resources.
|
||||
@ -547,12 +535,7 @@ func (b *BtcdNotifier) handleRelevantTx(tx *btcutil.Tx,
|
||||
// If this is a mempool spend, we'll ask the mempool notifier to hanlde
|
||||
// it.
|
||||
if mempool {
|
||||
err := b.memNotifier.ProcessRelevantSpendTx(tx)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Errorf("Unable to process transaction "+
|
||||
"%v: %v", tx.Hash(), err)
|
||||
}
|
||||
|
||||
b.memNotifier.ProcessRelevantSpendTx(tx)
|
||||
return
|
||||
}
|
||||
|
||||
@ -678,7 +661,7 @@ func (b *BtcdNotifier) confDetailsManually(confRequest chainntnfs.ConfRequest,
|
||||
}
|
||||
|
||||
return &chainntnfs.TxConfirmation{
|
||||
Tx: tx.Copy(),
|
||||
Tx: tx,
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: height,
|
||||
TxIndex: uint32(txIndex),
|
||||
@ -703,7 +686,7 @@ func (b *BtcdNotifier) handleBlockConnected(epoch chainntnfs.BlockEpoch) error {
|
||||
// clients.
|
||||
rawBlock, err := b.GetBlock(epoch.Hash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get block: %w", err)
|
||||
return fmt.Errorf("unable to get block: %v", err)
|
||||
}
|
||||
newBlock := &filteredBlock{
|
||||
hash: *epoch.Hash,
|
||||
@ -717,7 +700,7 @@ func (b *BtcdNotifier) handleBlockConnected(epoch chainntnfs.BlockEpoch) error {
|
||||
// us.
|
||||
err = b.txNotifier.ConnectTip(newBlock.block, newBlock.height)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to connect tip: %w", err)
|
||||
return fmt.Errorf("unable to connect tip: %v", err)
|
||||
}
|
||||
|
||||
chainntnfs.Log.Infof("New block: height=%v, sha=%v", epoch.Height,
|
||||
@ -796,8 +779,7 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
||||
pkScript, b.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse script: %w",
|
||||
err)
|
||||
return nil, fmt.Errorf("unable to parse script: %v", err)
|
||||
}
|
||||
if err := b.chainConn.NotifyReceived(addrs); err != nil {
|
||||
return nil, err
|
||||
@ -835,8 +817,7 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
||||
pkScript, b.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse address: %w",
|
||||
err)
|
||||
return nil, fmt.Errorf("unable to parse address: %v", err)
|
||||
}
|
||||
|
||||
asyncResult := b.chainConn.RescanAsync(startHash, addrs, nil)
|
||||
@ -897,8 +878,8 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
||||
// proceed with fallback methods.
|
||||
jsonErr, ok := err.(*btcjson.RPCError)
|
||||
if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo {
|
||||
return nil, fmt.Errorf("unable to query for txid %v: "+
|
||||
"%w", outpoint.Hash, err)
|
||||
return nil, fmt.Errorf("unable to query for txid %v: %v",
|
||||
outpoint.Hash, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1138,26 +1119,3 @@ func (b *BtcdNotifier) CancelMempoolSpendEvent(
|
||||
|
||||
b.memNotifier.UnsubscribeEvent(sub)
|
||||
}
|
||||
|
||||
// LookupInputMempoolSpend takes an outpoint and queries the mempool to find
|
||||
// its spending tx. Returns the tx if found, otherwise fn.None.
|
||||
//
|
||||
// NOTE: part of the MempoolWatcher interface.
|
||||
func (b *BtcdNotifier) LookupInputMempoolSpend(
|
||||
op wire.OutPoint) fn.Option[wire.MsgTx] {
|
||||
|
||||
// Find the spending txid.
|
||||
txid, found := b.chainConn.LookupInputMempoolSpend(op)
|
||||
if !found {
|
||||
return fn.None[wire.MsgTx]()
|
||||
}
|
||||
|
||||
// Query the spending tx using the id.
|
||||
tx, err := b.chainConn.GetRawTransaction(&txid)
|
||||
if err != nil {
|
||||
// TODO(yy): enable logging errors in this package.
|
||||
return fn.None[wire.MsgTx]()
|
||||
}
|
||||
|
||||
return fn.Some(*tx.MsgTx().Copy())
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ import (
|
||||
"github.com/lightningnetwork/lnd/blockcache"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/lntest/unittest"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -56,7 +55,7 @@ func setUpNotifier(t *testing.T, h *rpctest.Harness) *BtcdNotifier {
|
||||
|
||||
rpcCfg := h.RPCConfig()
|
||||
notifier, err := New(
|
||||
&rpcCfg, unittest.NetParams, hintCache, hintCache, blockCache,
|
||||
&rpcCfg, chainntnfs.NetParams, hintCache, hintCache, blockCache,
|
||||
)
|
||||
require.NoError(t, err, "unable to create notifier")
|
||||
if err := notifier.Start(); err != nil {
|
||||
@ -74,8 +73,8 @@ func setUpNotifier(t *testing.T, h *rpctest.Harness) *BtcdNotifier {
|
||||
func TestHistoricalConfDetailsTxIndex(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
harness := unittest.NewMiner(
|
||||
t, unittest.NetParams, []string{"--txindex"}, true, 25,
|
||||
harness := chainntnfs.NewMiner(
|
||||
t, []string{"--txindex"}, true, 25,
|
||||
)
|
||||
|
||||
notifier := setUpNotifier(t, harness)
|
||||
@ -146,7 +145,7 @@ func TestHistoricalConfDetailsTxIndex(t *testing.T) {
|
||||
func TestHistoricalConfDetailsNoTxIndex(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
harness := unittest.NewMiner(t, unittest.NetParams, nil, true, 25)
|
||||
harness := chainntnfs.NewMiner(t, nil, true, 25)
|
||||
|
||||
notifier := setUpNotifier(t, harness)
|
||||
|
||||
|
||||
@ -13,7 +13,6 @@ import (
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -279,34 +278,6 @@ type SpendDetail struct {
|
||||
SpendingHeight int32
|
||||
}
|
||||
|
||||
// HasSpenderWitness returns true if the spending transaction has non-empty
|
||||
// witness.
|
||||
func (s *SpendDetail) HasSpenderWitness() bool {
|
||||
tx := s.SpendingTx
|
||||
|
||||
// If there are no inputs, then there is no witness.
|
||||
if len(tx.TxIn) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the spender input index is larger than the number of inputs, then
|
||||
// we don't have a witness and this is an error case so we log it.
|
||||
if uint32(len(tx.TxIn)) <= s.SpenderInputIndex {
|
||||
Log.Errorf("SpenderInputIndex %d is out of range for tx %v",
|
||||
s.SpenderInputIndex, tx.TxHash())
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// If the witness is empty, then there is no witness.
|
||||
if len(tx.TxIn[s.SpenderInputIndex].Witness) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the witness is non-empty, then we have a witness.
|
||||
return true
|
||||
}
|
||||
|
||||
// String returns a string representation of SpendDetail.
|
||||
func (s *SpendDetail) String() string {
|
||||
return fmt.Sprintf("%v[%d] spending %v at height=%v", s.SpenderTxHash,
|
||||
@ -484,13 +455,13 @@ func GetCommonBlockAncestorHeight(chainConn ChainConn, reorgHash,
|
||||
for reorgHash != chainHash {
|
||||
reorgHeader, err := chainConn.GetBlockHeader(&reorgHash)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to get header for "+
|
||||
"hash=%v: %w", reorgHash, err)
|
||||
return 0, fmt.Errorf("unable to get header for hash=%v: %v",
|
||||
reorgHash, err)
|
||||
}
|
||||
chainHeader, err := chainConn.GetBlockHeader(&chainHash)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to get header for "+
|
||||
"hash=%v: %w", chainHash, err)
|
||||
return 0, fmt.Errorf("unable to get header for hash=%v: %v",
|
||||
chainHash, err)
|
||||
}
|
||||
reorgHash = reorgHeader.PrevBlock
|
||||
chainHash = chainHeader.PrevBlock
|
||||
@ -498,8 +469,8 @@ func GetCommonBlockAncestorHeight(chainConn ChainConn, reorgHash,
|
||||
|
||||
verboseHeader, err := chainConn.GetBlockHeaderVerbose(&chainHash)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to get verbose header for "+
|
||||
"hash=%v: %w", chainHash, err)
|
||||
return 0, fmt.Errorf("unable to get verbose header for hash=%v: %v",
|
||||
chainHash, err)
|
||||
}
|
||||
|
||||
return verboseHeader.Height, nil
|
||||
@ -542,7 +513,7 @@ func GetClientMissedBlocks(chainConn ChainConn, clientBestBlock *BlockEpoch,
|
||||
chainConn, startingHeight+1, notifierBestHeight+1,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get missed blocks: %w", err)
|
||||
return nil, fmt.Errorf("unable to get missed blocks: %v", err)
|
||||
}
|
||||
|
||||
return missedBlocks, nil
|
||||
@ -720,7 +691,7 @@ func ConfDetailsFromTxIndex(chainConn TxIndexConn, r ConfRequest,
|
||||
}
|
||||
|
||||
return nil, TxNotFoundIndex,
|
||||
fmt.Errorf("unable to query for txid %v: %w",
|
||||
fmt.Errorf("unable to query for txid %v: %v",
|
||||
r.TxID, err)
|
||||
}
|
||||
|
||||
@ -729,13 +700,13 @@ func ConfDetailsFromTxIndex(chainConn TxIndexConn, r ConfRequest,
|
||||
rawTx, err := hex.DecodeString(rawTxRes.Hex)
|
||||
if err != nil {
|
||||
return nil, TxNotFoundIndex,
|
||||
fmt.Errorf("unable to deserialize tx %v: %w",
|
||||
fmt.Errorf("unable to deserialize tx %v: %v",
|
||||
r.TxID, err)
|
||||
}
|
||||
var tx wire.MsgTx
|
||||
if err := tx.Deserialize(bytes.NewReader(rawTx)); err != nil {
|
||||
return nil, TxNotFoundIndex,
|
||||
fmt.Errorf("unable to deserialize tx %v: %w",
|
||||
fmt.Errorf("unable to deserialize tx %v: %v",
|
||||
r.TxID, err)
|
||||
}
|
||||
|
||||
@ -760,14 +731,13 @@ func ConfDetailsFromTxIndex(chainConn TxIndexConn, r ConfRequest,
|
||||
if err != nil {
|
||||
return nil, TxNotFoundIndex,
|
||||
fmt.Errorf("unable to get block hash %v for "+
|
||||
"historical dispatch: %w", rawTxRes.BlockHash,
|
||||
err)
|
||||
"historical dispatch: %v", rawTxRes.BlockHash, err)
|
||||
}
|
||||
block, err := chainConn.GetBlock(blockHash)
|
||||
if err != nil {
|
||||
return nil, TxNotFoundIndex,
|
||||
fmt.Errorf("unable to get block with hash %v for "+
|
||||
"historical dispatch: %w", blockHash, err)
|
||||
"historical dispatch: %v", blockHash, err)
|
||||
}
|
||||
|
||||
// In the modern chain (the only one we really care about for LN), the
|
||||
@ -790,7 +760,7 @@ func ConfDetailsFromTxIndex(chainConn TxIndexConn, r ConfRequest,
|
||||
}
|
||||
|
||||
return &TxConfirmation{
|
||||
Tx: tx.Copy(),
|
||||
Tx: &tx,
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: uint32(blockHeight),
|
||||
TxIndex: uint32(txIndex),
|
||||
@ -850,9 +820,4 @@ type MempoolWatcher interface {
|
||||
// CancelMempoolSpendEvent allows the caller to cancel a subscription to
|
||||
// watch for a spend of an outpoint in the mempool.
|
||||
CancelMempoolSpendEvent(sub *MempoolSpendEvent)
|
||||
|
||||
// LookupInputMempoolSpend looks up the mempool to find a spending tx
|
||||
// which spends the given outpoint. A fn.None is returned if it's not
|
||||
// found.
|
||||
LookupInputMempoolSpend(op wire.OutPoint) fn.Option[wire.MsgTx]
|
||||
}
|
||||
|
||||
@ -146,13 +146,7 @@ func (m *MempoolNotifier) UnsubsribeConfirmedSpentTx(tx *btcutil.Tx) {
|
||||
Log.Tracef("Unsubscribe confirmed tx %s", tx.Hash())
|
||||
|
||||
// Get the spent inputs of interest.
|
||||
spentInputs, err := m.findRelevantInputs(tx)
|
||||
if err != nil {
|
||||
Log.Errorf("Unable to find relevant inputs for tx %s: %v",
|
||||
tx.Hash(), err)
|
||||
|
||||
return
|
||||
}
|
||||
spentInputs := m.findRelevantInputs(tx)
|
||||
|
||||
// Unsubscribe the subscribers.
|
||||
for outpoint := range spentInputs {
|
||||
@ -166,36 +160,27 @@ func (m *MempoolNotifier) UnsubsribeConfirmedSpentTx(tx *btcutil.Tx) {
|
||||
// ProcessRelevantSpendTx takes a transaction and checks whether it spends any
|
||||
// of the subscribed inputs. If so, spend notifications are sent to the
|
||||
// relevant subscribers.
|
||||
func (m *MempoolNotifier) ProcessRelevantSpendTx(tx *btcutil.Tx) error {
|
||||
func (m *MempoolNotifier) ProcessRelevantSpendTx(tx *btcutil.Tx) {
|
||||
Log.Tracef("Processing mempool tx %s", tx.Hash())
|
||||
defer Log.Tracef("Finished processing mempool tx %s", tx.Hash())
|
||||
|
||||
// Get the spent inputs of interest.
|
||||
spentInputs, err := m.findRelevantInputs(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
spentInputs := m.findRelevantInputs(tx)
|
||||
|
||||
// Notify the subscribers.
|
||||
m.notifySpent(spentInputs)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TearDown stops the notifier and cleans up resources.
|
||||
func (m *MempoolNotifier) TearDown() {
|
||||
Log.Infof("Stopping mempool notifier")
|
||||
defer Log.Debug("mempool notifier stopped")
|
||||
|
||||
close(m.quit)
|
||||
m.wg.Wait()
|
||||
}
|
||||
|
||||
// findRelevantInputs takes a transaction to find the subscribed inputs and
|
||||
// returns them.
|
||||
func (m *MempoolNotifier) findRelevantInputs(tx *btcutil.Tx) (inputsWithTx,
|
||||
error) {
|
||||
|
||||
func (m *MempoolNotifier) findRelevantInputs(tx *btcutil.Tx) inputsWithTx {
|
||||
txid := tx.Hash()
|
||||
watchedInputs := make(inputsWithTx)
|
||||
|
||||
@ -222,23 +207,9 @@ func (m *MempoolNotifier) findRelevantInputs(tx *btcutil.Tx) (inputsWithTx,
|
||||
SpendingHeight: 0,
|
||||
}
|
||||
watchedInputs[*op] = details
|
||||
|
||||
// Sanity check the witness stack. If it's not empty, continue
|
||||
// to next iteration.
|
||||
if details.HasSpenderWitness() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Return an error if the witness data is not present in the
|
||||
// spending transaction.
|
||||
Log.Criticalf("Found spending tx for outpoint=%v in mempool, "+
|
||||
"but the transaction %v does not have witness",
|
||||
op, details.SpendingTx.TxHash())
|
||||
|
||||
return nil, ErrEmptyWitnessStack
|
||||
}
|
||||
|
||||
return watchedInputs, nil
|
||||
return watchedInputs
|
||||
}
|
||||
|
||||
// notifySpent iterates all the spentInputs and notifies the subscribers about
|
||||
|
||||
@ -11,9 +11,6 @@ import (
|
||||
|
||||
const testTimeout = 5 * time.Second
|
||||
|
||||
// dummyWitness is used to fill the witness data in a transaction.
|
||||
var dummyWitness = [][]byte{{0x01}}
|
||||
|
||||
// TestMempoolSubscribeInput tests that we can successfully subscribe an input.
|
||||
func TestMempoolSubscribeInput(t *testing.T) {
|
||||
t.Parallel()
|
||||
@ -110,9 +107,9 @@ func TestMempoolUnsubscribeEvent(t *testing.T) {
|
||||
require.True(t, loaded)
|
||||
}
|
||||
|
||||
// TestMempoolFindRelevantInputsEmptyWitness tests that the mempool notifier
|
||||
// returns an error when the witness stack is empty.
|
||||
func TestMempoolFindRelevantInputsEmptyWitness(t *testing.T) {
|
||||
// TestMempoolFindRelevantInputs tests that the mempool notifier can find the
|
||||
// spend of subscribed inputs from a given transaction.
|
||||
func TestMempoolFindRelevantInputs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a new mempool notifier instance.
|
||||
@ -135,37 +132,6 @@ func TestMempoolFindRelevantInputsEmptyWitness(t *testing.T) {
|
||||
}
|
||||
tx := btcutil.NewTx(msgTx)
|
||||
|
||||
// Call the method.
|
||||
result, err := notifier.findRelevantInputs(tx)
|
||||
require.ErrorIs(t, err, ErrEmptyWitnessStack)
|
||||
require.Nil(t, result)
|
||||
}
|
||||
|
||||
// TestMempoolFindRelevantInputs tests that the mempool notifier can find the
|
||||
// spend of subscribed inputs from a given transaction.
|
||||
func TestMempoolFindRelevantInputs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a new mempool notifier instance.
|
||||
notifier := NewMempoolNotifier()
|
||||
|
||||
// Create two inputs and subscribe to the second one.
|
||||
input1 := wire.OutPoint{Hash: [32]byte{1}}
|
||||
input2 := wire.OutPoint{Hash: [32]byte{2}}
|
||||
|
||||
// Make input2 the subscribed input.
|
||||
notifier.SubscribeInput(input2)
|
||||
|
||||
// Create a transaction that spends the above two inputs.
|
||||
msgTx := &wire.MsgTx{
|
||||
TxIn: []*wire.TxIn{
|
||||
{PreviousOutPoint: input1, Witness: dummyWitness},
|
||||
{PreviousOutPoint: input2, Witness: dummyWitness},
|
||||
},
|
||||
TxOut: []*wire.TxOut{},
|
||||
}
|
||||
tx := btcutil.NewTx(msgTx)
|
||||
|
||||
// Create the expected spend detail.
|
||||
detailExp := &SpendDetail{
|
||||
SpentOutPoint: &input2,
|
||||
@ -175,8 +141,7 @@ func TestMempoolFindRelevantInputs(t *testing.T) {
|
||||
}
|
||||
|
||||
// Call the method.
|
||||
result, err := notifier.findRelevantInputs(tx)
|
||||
require.NoError(t, err)
|
||||
result := notifier.findRelevantInputs(tx)
|
||||
|
||||
// Verify that the result is as expected.
|
||||
require.Contains(t, result, input2)
|
||||
@ -400,7 +365,7 @@ func TestMempoolUnsubscribeConfirmedSpentTx(t *testing.T) {
|
||||
// Create a transaction that spends input1.
|
||||
msgTx := &wire.MsgTx{
|
||||
TxIn: []*wire.TxIn{
|
||||
{PreviousOutPoint: input1, Witness: dummyWitness},
|
||||
{PreviousOutPoint: input1},
|
||||
},
|
||||
}
|
||||
tx := btcutil.NewTx(msgTx)
|
||||
|
||||
@ -1,123 +0,0 @@
|
||||
package chainntnfs
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockMempoolWatcher is a mock implementation of the MempoolWatcher interface.
|
||||
// This is used by other subsystems to mock the behavior of the mempool
|
||||
// watcher.
|
||||
type MockMempoolWatcher struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// NewMockMempoolWatcher returns a new instance of a mock mempool watcher.
|
||||
func NewMockMempoolWatcher() *MockMempoolWatcher {
|
||||
return &MockMempoolWatcher{}
|
||||
}
|
||||
|
||||
// Compile-time check to ensure MockMempoolWatcher implements MempoolWatcher.
|
||||
var _ MempoolWatcher = (*MockMempoolWatcher)(nil)
|
||||
|
||||
// SubscribeMempoolSpent implements the MempoolWatcher interface.
|
||||
func (m *MockMempoolWatcher) SubscribeMempoolSpent(
|
||||
op wire.OutPoint) (*MempoolSpendEvent, error) {
|
||||
|
||||
args := m.Called(op)
|
||||
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(*MempoolSpendEvent), args.Error(1)
|
||||
}
|
||||
|
||||
// CancelMempoolSpendEvent implements the MempoolWatcher interface.
|
||||
func (m *MockMempoolWatcher) CancelMempoolSpendEvent(
|
||||
sub *MempoolSpendEvent) {
|
||||
|
||||
m.Called(sub)
|
||||
}
|
||||
|
||||
// LookupInputMempoolSpend looks up the mempool to find a spending tx which
|
||||
// spends the given outpoint.
|
||||
func (m *MockMempoolWatcher) LookupInputMempoolSpend(
|
||||
op wire.OutPoint) fn.Option[wire.MsgTx] {
|
||||
|
||||
args := m.Called(op)
|
||||
|
||||
return args.Get(0).(fn.Option[wire.MsgTx])
|
||||
}
|
||||
|
||||
// MockNotifier is a mock implementation of the ChainNotifier interface.
|
||||
type MockChainNotifier struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Compile-time check to ensure MockChainNotifier implements ChainNotifier.
|
||||
var _ ChainNotifier = (*MockChainNotifier)(nil)
|
||||
|
||||
// RegisterConfirmationsNtfn registers an intent to be notified once txid
|
||||
// reaches numConfs confirmations.
|
||||
func (m *MockChainNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
||||
pkScript []byte, numConfs, heightHint uint32,
|
||||
opts ...NotifierOption) (*ConfirmationEvent, error) {
|
||||
|
||||
args := m.Called(txid, pkScript, numConfs, heightHint)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(*ConfirmationEvent), args.Error(1)
|
||||
}
|
||||
|
||||
// RegisterSpendNtfn registers an intent to be notified once the target
|
||||
// outpoint is successfully spent within a transaction.
|
||||
func (m *MockChainNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
||||
pkScript []byte, heightHint uint32) (*SpendEvent, error) {
|
||||
|
||||
args := m.Called(outpoint, pkScript, heightHint)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(*SpendEvent), args.Error(1)
|
||||
}
|
||||
|
||||
// RegisterBlockEpochNtfn registers an intent to be notified of each new block
|
||||
// connected to the tip of the main chain.
|
||||
func (m *MockChainNotifier) RegisterBlockEpochNtfn(epoch *BlockEpoch) (
|
||||
*BlockEpochEvent, error) {
|
||||
|
||||
args := m.Called(epoch)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(*BlockEpochEvent), args.Error(1)
|
||||
}
|
||||
|
||||
// Start the ChainNotifier. Once started, the implementation should be ready,
|
||||
// and able to receive notification registrations from clients.
|
||||
func (m *MockChainNotifier) Start() error {
|
||||
args := m.Called()
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
// Started returns true if this instance has been started, and false otherwise.
|
||||
func (m *MockChainNotifier) Started() bool {
|
||||
args := m.Called()
|
||||
|
||||
return args.Bool(0)
|
||||
}
|
||||
|
||||
// Stops the concrete ChainNotifier.
|
||||
func (m *MockChainNotifier) Stop() error {
|
||||
args := m.Called()
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
@ -136,8 +136,7 @@ func (n *NeutrinoNotifier) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
chainntnfs.Log.Info("neutrino notifier shutting down...")
|
||||
defer chainntnfs.Log.Debug("neutrino notifier shutdown complete")
|
||||
chainntnfs.Log.Info("neutrino notifier shutting down")
|
||||
|
||||
close(n.quit)
|
||||
n.wg.Wait()
|
||||
@ -152,12 +151,7 @@ func (n *NeutrinoNotifier) Stop() error {
|
||||
|
||||
close(epochClient.epochChan)
|
||||
}
|
||||
|
||||
// The txNotifier is only initialized in the start method therefore we
|
||||
// need to make sure we don't access a nil pointer here.
|
||||
if n.txNotifier != nil {
|
||||
n.txNotifier.TearDown()
|
||||
}
|
||||
n.txNotifier.TearDown()
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -587,8 +581,8 @@ func (n *NeutrinoNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequ
|
||||
// can compute the current block hash.
|
||||
blockHash, err := n.p2pNode.GetBlockHash(int64(scanHeight))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get header for "+
|
||||
"height=%v: %w", scanHeight, err)
|
||||
return nil, fmt.Errorf("unable to get header for height=%v: %v",
|
||||
scanHeight, err)
|
||||
}
|
||||
|
||||
// With the hash computed, we can now fetch the basic filter for this
|
||||
@ -605,8 +599,8 @@ func (n *NeutrinoNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequ
|
||||
neutrino.MaxBatchSize(int64(scanHeight-startHeight+1)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to retrieve regular "+
|
||||
"filter for height=%v: %w", scanHeight, err)
|
||||
return nil, fmt.Errorf("unable to retrieve regular filter for "+
|
||||
"height=%v: %v", scanHeight, err)
|
||||
}
|
||||
|
||||
// In the case that the filter exists, we'll attempt to see if
|
||||
@ -614,8 +608,7 @@ func (n *NeutrinoNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequ
|
||||
key := builder.DeriveKey(blockHash)
|
||||
match, err := regFilter.Match(key, confRequest.PkScript.Script())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to query filter: %w",
|
||||
err)
|
||||
return nil, fmt.Errorf("unable to query filter: %v", err)
|
||||
}
|
||||
|
||||
// If there's no match, then we can continue forward to the
|
||||
@ -629,8 +622,7 @@ func (n *NeutrinoNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequ
|
||||
// to send the proper response.
|
||||
block, err := n.GetBlock(*blockHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get block from "+
|
||||
"network: %w", err)
|
||||
return nil, fmt.Errorf("unable to get block from network: %v", err)
|
||||
}
|
||||
|
||||
// For every transaction in the block, check which one matches
|
||||
@ -642,7 +634,7 @@ func (n *NeutrinoNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequ
|
||||
}
|
||||
|
||||
return &chainntnfs.TxConfirmation{
|
||||
Tx: tx.MsgTx().Copy(),
|
||||
Tx: tx.MsgTx(),
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: scanHeight,
|
||||
TxIndex: uint32(i),
|
||||
@ -670,11 +662,11 @@ func (n *NeutrinoNotifier) handleBlockConnected(newBlock *filteredBlock) error {
|
||||
// result in the items we care about being dispatched.
|
||||
rawBlock, err := n.GetBlock(newBlock.hash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get full block: %w", err)
|
||||
return fmt.Errorf("unable to get full block: %v", err)
|
||||
}
|
||||
err = n.txNotifier.ConnectTip(rawBlock, newBlock.height)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to connect tip: %w", err)
|
||||
return fmt.Errorf("unable to connect tip: %v", err)
|
||||
}
|
||||
|
||||
chainntnfs.Log.Infof("New block: height=%v, sha=%v", newBlock.height,
|
||||
@ -699,7 +691,7 @@ func (n *NeutrinoNotifier) handleBlockConnected(newBlock *filteredBlock) error {
|
||||
func (n *NeutrinoNotifier) getFilteredBlock(epoch chainntnfs.BlockEpoch) (*filteredBlock, error) {
|
||||
rawBlock, err := n.GetBlock(*epoch.Hash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get block: %w", err)
|
||||
return nil, fmt.Errorf("unable to get block: %v", err)
|
||||
}
|
||||
|
||||
txns := rawBlock.Transactions()
|
||||
@ -807,7 +799,7 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
||||
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to update filter: %w", err)
|
||||
return nil, fmt.Errorf("unable to update filter: %v", err)
|
||||
}
|
||||
|
||||
// If the txNotifier didn't return any details to perform a historical
|
||||
@ -944,7 +936,7 @@ func (n *NeutrinoNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
||||
params := n.p2pNode.ChainParams()
|
||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, ¶ms)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract script: %w", err)
|
||||
return nil, fmt.Errorf("unable to extract script: %v", err)
|
||||
}
|
||||
|
||||
// We'll send the filter update request to the notifier's main event
|
||||
@ -969,7 +961,7 @@ func (n *NeutrinoNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
||||
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to update filter: %w", err)
|
||||
return nil, fmt.Errorf("unable to update filter: %v", err)
|
||||
}
|
||||
|
||||
// If a historical rescan was not requested by the txNotifier, then we
|
||||
|
||||
@ -25,8 +25,6 @@ import (
|
||||
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/lntest/unittest"
|
||||
"github.com/lightningnetwork/lnd/lnutils"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -1044,9 +1042,14 @@ func testReorgConf(miner *rpctest.Harness,
|
||||
notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
|
||||
|
||||
// Set up a new miner that we can use to cause a reorg.
|
||||
miner2 := unittest.NewMiner(
|
||||
t, unittest.NetParams, []string{"--txindex"}, false, 0,
|
||||
miner2, err := rpctest.New(
|
||||
chainntnfs.NetParams, nil, []string{"--txindex"}, "",
|
||||
)
|
||||
require.NoError(t, err, "unable to create mining node")
|
||||
if err := miner2.SetUp(false, 0); err != nil {
|
||||
t.Fatalf("unable to set up mining node: %v", err)
|
||||
}
|
||||
defer miner2.TearDown()
|
||||
|
||||
// We start by connecting the new miner to our original miner,
|
||||
// such that it will sync to our original chain.
|
||||
@ -1200,9 +1203,14 @@ func testReorgSpend(miner *rpctest.Harness,
|
||||
require.NoError(t, err, "unable to register for spend")
|
||||
|
||||
// Set up a new miner that we can use to cause a reorg.
|
||||
miner2 := unittest.NewMiner(
|
||||
t, unittest.NetParams, []string{"--txindex"}, false, 0,
|
||||
miner2, err := rpctest.New(
|
||||
chainntnfs.NetParams, nil, []string{"--txindex"}, "",
|
||||
)
|
||||
require.NoError(t, err, "unable to create mining node")
|
||||
if err := miner2.SetUp(false, 0); err != nil {
|
||||
t.Fatalf("unable to set up mining node: %v", err)
|
||||
}
|
||||
defer miner2.TearDown()
|
||||
|
||||
// We start by connecting the new miner to our original miner, in order
|
||||
// to have a consistent view of the chain from both miners. They should
|
||||
@ -1518,9 +1526,14 @@ func testCatchUpOnMissedBlocksWithReorg(miner1 *rpctest.Harness,
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Set up a new miner that we can use to cause a reorg.
|
||||
miner2 := unittest.NewMiner(
|
||||
t, unittest.NetParams, []string{"--txindex"}, false, 0,
|
||||
miner2, err := rpctest.New(
|
||||
chainntnfs.NetParams, nil, []string{"--txindex"}, "",
|
||||
)
|
||||
require.NoError(t, err, "unable to create mining node")
|
||||
if err := miner2.SetUp(false, 0); err != nil {
|
||||
t.Fatalf("unable to set up mining node: %v", err)
|
||||
}
|
||||
defer miner2.TearDown()
|
||||
|
||||
// We start by connecting the new miner to our original miner,
|
||||
// such that it will sync to our original chain.
|
||||
@ -1696,90 +1709,6 @@ func testCatchUpOnMissedBlocksWithReorg(miner1 *rpctest.Harness,
|
||||
}
|
||||
}
|
||||
|
||||
// testIncludeBlockAsymmetry tests that if the same output is registered for a
|
||||
// notification by two callers, one is able to get a notification with the
|
||||
// block and the other one without it.
|
||||
func testIncludeBlockAsymmetry(miner *rpctest.Harness,
|
||||
notifier chainntnfs.TestChainNotifier, scriptDispatch bool,
|
||||
t *testing.T) {
|
||||
|
||||
// We'll start by creating a new test transaction, waiting long enough
|
||||
// for it to get into the mempool.
|
||||
txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner)
|
||||
require.NoError(t, err, "unable to create test tx")
|
||||
if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
_, currentHeight, err := miner.Client.GetBestBlock()
|
||||
require.NoError(t, err, "unable to get current height")
|
||||
|
||||
var (
|
||||
confIntentNoBlock *chainntnfs.ConfirmationEvent
|
||||
confIntentBlock *chainntnfs.ConfirmationEvent
|
||||
|
||||
numConfsLong = uint32(6)
|
||||
numConfsShort = uint32(1)
|
||||
)
|
||||
dispatchClients := func() {
|
||||
dispatchTxid := txid
|
||||
if scriptDispatch {
|
||||
dispatchTxid = nil
|
||||
}
|
||||
|
||||
confIntentNoBlock, err = notifier.RegisterConfirmationsNtfn(
|
||||
dispatchTxid, pkScript, numConfsLong,
|
||||
uint32(currentHeight),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
confIntentBlock, err = notifier.RegisterConfirmationsNtfn(
|
||||
dispatchTxid, pkScript, numConfsShort,
|
||||
uint32(currentHeight), chainntnfs.WithIncludeBlock(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assertNtfns := func() {
|
||||
// Make sure the long confirmation client receives the
|
||||
// notification but not the block.
|
||||
confNtfnNoBlock, err := lnutils.RecvOrTimeout(
|
||||
confIntentNoBlock.Confirmed, time.Second*5,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, (*confNtfnNoBlock).Block, "block not included")
|
||||
|
||||
// And the short confirmation client receives the notification
|
||||
// and the block.
|
||||
confNtfnBlock, err := lnutils.RecvOrTimeout(
|
||||
confIntentBlock.Confirmed, time.Second*5,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, (*confNtfnBlock).Block, "block included")
|
||||
}
|
||||
|
||||
// First, we start off by registering two clients for the same txid and
|
||||
// pkScript. One of them will require 6 confirmations but not request
|
||||
// the block, the other will just require a single confirmation and
|
||||
// request the block.
|
||||
dispatchClients()
|
||||
|
||||
// Next, we'll generate a few blocks, which should confirm the
|
||||
// transaction created above and trigger the notifications, as it should
|
||||
// be confirmed enough for both clients.
|
||||
_, err = miner.Client.Generate(6)
|
||||
require.NoError(t, err, "unable to generate single block")
|
||||
|
||||
// Make sure we got the notifications we expected.
|
||||
assertNtfns()
|
||||
|
||||
// Now dispatch the same clients again, which should hit the same
|
||||
// conditions as above and use the cached rescan details.
|
||||
dispatchClients()
|
||||
|
||||
// And again, the notifications should be triggered as expected.
|
||||
assertNtfns()
|
||||
}
|
||||
|
||||
type txNtfnTestCase struct {
|
||||
name string
|
||||
test func(node *rpctest.Harness, notifier chainntnfs.TestChainNotifier,
|
||||
@ -1843,10 +1772,6 @@ var txNtfnTests = []txNtfnTestCase{
|
||||
name: "cancel spend ntfn",
|
||||
test: testCancelSpendNtfn,
|
||||
},
|
||||
{
|
||||
name: "test include block asymmetry",
|
||||
test: testIncludeBlockAsymmetry,
|
||||
},
|
||||
}
|
||||
|
||||
var blockNtfnTests = []blockNtfnTestCase{
|
||||
@ -1891,8 +1816,9 @@ func TestInterfaces(t *testing.T, targetBackEnd string) {
|
||||
// dedicated miner to generate blocks, cause re-orgs, etc. We'll set up
|
||||
// this node with a chain length of 125, so we have plenty of BTC to
|
||||
// play around with.
|
||||
miner := unittest.NewMiner(t, unittest.NetParams, nil, true, 25)
|
||||
miner := chainntnfs.NewMiner(t, nil, true, 25)
|
||||
|
||||
rpcConfig := miner.RPCConfig()
|
||||
p2pAddr := miner.P2PAddress()
|
||||
|
||||
log.Printf("Running %v ChainNotifier interface tests",
|
||||
@ -1929,42 +1855,39 @@ func TestInterfaces(t *testing.T, targetBackEnd string) {
|
||||
switch notifierType {
|
||||
case "bitcoind":
|
||||
var bitcoindConn *chain.BitcoindConn
|
||||
bitcoindConn = unittest.NewBitcoindBackend(
|
||||
t, unittest.NetParams, p2pAddr, true, false,
|
||||
bitcoindConn = chainntnfs.NewBitcoindBackend(
|
||||
t, p2pAddr, true, false,
|
||||
)
|
||||
newNotifier = func() (chainntnfs.TestChainNotifier, error) {
|
||||
return bitcoindnotify.New(
|
||||
bitcoindConn, unittest.NetParams,
|
||||
bitcoindConn, chainntnfs.NetParams,
|
||||
hintCache, hintCache, blockCache,
|
||||
), nil
|
||||
}
|
||||
|
||||
case "bitcoind-rpc-polling":
|
||||
var bitcoindConn *chain.BitcoindConn
|
||||
bitcoindConn = unittest.NewBitcoindBackend(
|
||||
t, unittest.NetParams, p2pAddr, true, true,
|
||||
bitcoindConn = chainntnfs.NewBitcoindBackend(
|
||||
t, p2pAddr, true, true,
|
||||
)
|
||||
newNotifier = func() (chainntnfs.TestChainNotifier, error) {
|
||||
return bitcoindnotify.New(
|
||||
bitcoindConn, unittest.NetParams,
|
||||
bitcoindConn, chainntnfs.NetParams,
|
||||
hintCache, hintCache, blockCache,
|
||||
), nil
|
||||
}
|
||||
|
||||
case "btcd":
|
||||
rpcConfig := miner.RPCConfig()
|
||||
newNotifier = func() (chainntnfs.TestChainNotifier, error) {
|
||||
return btcdnotify.New(
|
||||
&rpcConfig, unittest.NetParams,
|
||||
&rpcConfig, chainntnfs.NetParams,
|
||||
hintCache, hintCache, blockCache,
|
||||
)
|
||||
}
|
||||
|
||||
case "neutrino":
|
||||
var spvNode *neutrino.ChainService
|
||||
spvNode = unittest.NewNeutrinoBackend(
|
||||
t, unittest.NetParams, p2pAddr,
|
||||
)
|
||||
spvNode = chainntnfs.NewNeutrinoBackend(t, p2pAddr)
|
||||
newNotifier = func() (chainntnfs.TestChainNotifier, error) {
|
||||
return neutrinonotify.New(
|
||||
spvNode, hintCache, hintCache,
|
||||
|
||||
@ -6,18 +6,26 @@ package chainntnfs
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"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/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lntest/unittest"
|
||||
"github.com/btcsuite/btcwallet/chain"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/lightninglabs/neutrino"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -28,6 +36,10 @@ var (
|
||||
TrickleInterval = 10 * time.Millisecond
|
||||
)
|
||||
|
||||
var (
|
||||
NetParams = &chaincfg.RegressionNetParams
|
||||
)
|
||||
|
||||
// randPubKeyHashScript generates a P2PKH script that pays to the public key of
|
||||
// a randomly-generated private key.
|
||||
func randPubKeyHashScript() ([]byte, *btcec.PrivateKey, error) {
|
||||
@ -37,9 +49,7 @@ func randPubKeyHashScript() ([]byte, *btcec.PrivateKey, error) {
|
||||
}
|
||||
|
||||
pubKeyHash := btcutil.Hash160(privKey.PubKey().SerializeCompressed())
|
||||
addrScript, err := btcutil.NewAddressWitnessPubKeyHash(
|
||||
pubKeyHash, unittest.NetParams,
|
||||
)
|
||||
addrScript, err := btcutil.NewAddressPubKeyHash(pubKeyHash, NetParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -57,8 +67,7 @@ func randPubKeyHashScript() ([]byte, *btcec.PrivateKey, error) {
|
||||
func GetTestTxidAndScript(h *rpctest.Harness) (*chainhash.Hash, []byte, error) {
|
||||
pkScript, _, err := randPubKeyHashScript()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to generate pkScript: %w",
|
||||
err)
|
||||
return nil, nil, fmt.Errorf("unable to generate pkScript: %v", err)
|
||||
}
|
||||
output := &wire.TxOut{Value: 2e8, PkScript: pkScript}
|
||||
txid, err := h.SendOutputs([]*wire.TxOut{output}, 10)
|
||||
@ -140,26 +149,173 @@ func CreateSpendTx(t *testing.T, prevOutPoint *wire.OutPoint,
|
||||
|
||||
t.Helper()
|
||||
|
||||
// Create a new output.
|
||||
outputAmt := int64(1e8)
|
||||
witnessProgram, _, err := randPubKeyHashScript()
|
||||
require.NoError(t, err, "unable to generate pkScript")
|
||||
output := wire.NewTxOut(outputAmt, witnessProgram)
|
||||
spendingTx := wire.NewMsgTx(1)
|
||||
spendingTx.AddTxIn(&wire.TxIn{PreviousOutPoint: *prevOutPoint})
|
||||
spendingTx.AddTxOut(&wire.TxOut{Value: 1e8, PkScript: prevOutput.PkScript})
|
||||
|
||||
// Create a new tx.
|
||||
tx := wire.NewMsgTx(2)
|
||||
tx.AddTxIn(wire.NewTxIn(prevOutPoint, nil, nil))
|
||||
tx.AddTxOut(output)
|
||||
|
||||
// Generate the witness.
|
||||
sigHashes := input.NewTxSigHashesV0Only(tx)
|
||||
witnessScript, err := txscript.WitnessSignature(
|
||||
tx, sigHashes, 0, prevOutput.Value, prevOutput.PkScript,
|
||||
txscript.SigHashAll, privKey, true,
|
||||
sigScript, err := txscript.SignatureScript(
|
||||
spendingTx, 0, prevOutput.PkScript, txscript.SigHashAll,
|
||||
privKey, true,
|
||||
)
|
||||
require.NoError(t, err, "unable to sign tx")
|
||||
spendingTx.TxIn[0].SignatureScript = sigScript
|
||||
|
||||
tx.TxIn[0].Witness = witnessScript
|
||||
|
||||
return tx
|
||||
return spendingTx
|
||||
}
|
||||
|
||||
// NewMiner spawns testing harness backed by a btcd node that can serve as a
|
||||
// miner.
|
||||
func NewMiner(t *testing.T, extraArgs []string, createChain bool,
|
||||
spendableOutputs uint32) *rpctest.Harness {
|
||||
|
||||
t.Helper()
|
||||
|
||||
// Add the trickle interval argument to the extra args.
|
||||
trickle := fmt.Sprintf("--trickleinterval=%v", TrickleInterval)
|
||||
extraArgs = append(extraArgs, trickle)
|
||||
|
||||
node, err := rpctest.New(NetParams, nil, extraArgs, "")
|
||||
require.NoError(t, err, "unable to create backend node")
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, node.TearDown())
|
||||
})
|
||||
|
||||
if err := node.SetUp(createChain, spendableOutputs); err != nil {
|
||||
t.Fatalf("unable to set up backend node: %v", err)
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
// NewBitcoindBackend spawns a new bitcoind node that connects to a miner at the
|
||||
// specified address. The txindex boolean can be set to determine whether the
|
||||
// backend node should maintain a transaction index. The rpcpolling boolean
|
||||
// can be set to determine whether bitcoind's RPC polling interface should be
|
||||
// used for block and tx notifications or if its ZMQ interface should be used.
|
||||
// A connection to the newly spawned bitcoind node is returned.
|
||||
func NewBitcoindBackend(t *testing.T, minerAddr string, txindex,
|
||||
rpcpolling bool) *chain.BitcoindConn {
|
||||
|
||||
t.Helper()
|
||||
|
||||
// We use ioutil.TempDir here instead of t.TempDir because some versions
|
||||
// of bitcoind complain about the zmq connection string formats when the
|
||||
// t.TempDir directory string is used.
|
||||
tempBitcoindDir, err := ioutil.TempDir("", "bitcoind")
|
||||
require.NoError(t, err, "unable to create temp dir")
|
||||
|
||||
rpcPort := rand.Intn(65536-1024) + 1024
|
||||
zmqBlockHost := "ipc:///" + tempBitcoindDir + "/blocks.socket"
|
||||
zmqTxHost := "ipc:///" + tempBitcoindDir + "/tx.socket"
|
||||
|
||||
args := []string{
|
||||
"-connect=" + minerAddr,
|
||||
"-datadir=" + tempBitcoindDir,
|
||||
"-regtest",
|
||||
"-rpcauth=weks:469e9bb14ab2360f8e226efed5ca6fd$507c670e800a952" +
|
||||
"84294edb5773b05544b220110063096c221be9933c82d38e1",
|
||||
fmt.Sprintf("-rpcport=%d", rpcPort),
|
||||
"-disablewallet",
|
||||
"-zmqpubrawblock=" + zmqBlockHost,
|
||||
"-zmqpubrawtx=" + zmqTxHost,
|
||||
}
|
||||
if txindex {
|
||||
args = append(args, "-txindex")
|
||||
}
|
||||
|
||||
bitcoind := exec.Command("bitcoind", args...)
|
||||
if err := bitcoind.Start(); err != nil {
|
||||
t.Fatalf("unable to start bitcoind: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = bitcoind.Process.Kill()
|
||||
_ = bitcoind.Wait()
|
||||
})
|
||||
|
||||
// Wait for the bitcoind instance to start up.
|
||||
host := fmt.Sprintf("127.0.0.1:%d", rpcPort)
|
||||
cfg := &chain.BitcoindConfig{
|
||||
ChainParams: NetParams,
|
||||
Host: host,
|
||||
User: "weks",
|
||||
Pass: "weks",
|
||||
// Fields only required for pruned nodes, not needed for these
|
||||
// tests.
|
||||
Dialer: nil,
|
||||
PrunedModeMaxPeers: 0,
|
||||
}
|
||||
|
||||
if rpcpolling {
|
||||
cfg.PollingConfig = &chain.PollingConfig{
|
||||
BlockPollingInterval: time.Millisecond * 20,
|
||||
TxPollingInterval: time.Millisecond * 20,
|
||||
}
|
||||
} else {
|
||||
cfg.ZMQConfig = &chain.ZMQConfig{
|
||||
ZMQBlockHost: zmqBlockHost,
|
||||
ZMQTxHost: zmqTxHost,
|
||||
ZMQReadDeadline: 5 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
var conn *chain.BitcoindConn
|
||||
err = wait.NoError(func() error {
|
||||
var err error
|
||||
conn, err = chain.NewBitcoindConn(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return conn.Start()
|
||||
}, 10*time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to establish connection to bitcoind: %v", err)
|
||||
}
|
||||
t.Cleanup(conn.Stop)
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
// NewNeutrinoBackend spawns a new neutrino node that connects to a miner at
|
||||
// the specified address.
|
||||
func NewNeutrinoBackend(t *testing.T, minerAddr string) *neutrino.ChainService {
|
||||
t.Helper()
|
||||
|
||||
spvDir := t.TempDir()
|
||||
|
||||
dbName := filepath.Join(spvDir, "neutrino.db")
|
||||
spvDatabase, err := walletdb.Create(
|
||||
"bdb", dbName, true, kvdb.DefaultDBTimeout,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create walletdb: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
spvDatabase.Close()
|
||||
})
|
||||
|
||||
// Create an instance of neutrino connected to the running btcd
|
||||
// instance.
|
||||
spvConfig := neutrino.Config{
|
||||
DataDir: spvDir,
|
||||
Database: spvDatabase,
|
||||
ChainParams: *NetParams,
|
||||
ConnectPeers: []string{minerAddr},
|
||||
}
|
||||
spvNode, err := neutrino.NewChainService(spvConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create neutrino: %v", err)
|
||||
}
|
||||
|
||||
// We'll also wait for the instance to sync up fully to the chain
|
||||
// generated by the btcd instance.
|
||||
spvNode.Start()
|
||||
for !spvNode.IsCurrent() {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
spvNode.Stop()
|
||||
})
|
||||
|
||||
return spvNode
|
||||
}
|
||||
|
||||
@ -74,11 +74,6 @@ var (
|
||||
// out of range.
|
||||
ErrNumConfsOutOfRange = fmt.Errorf("number of confirmations must be "+
|
||||
"between %d and %d", 1, MaxNumConfs)
|
||||
|
||||
// ErrEmptyWitnessStack is returned when a spending transaction has an
|
||||
// empty witness stack. More details in,
|
||||
// - https://github.com/bitcoin/bitcoin/issues/28730
|
||||
ErrEmptyWitnessStack = errors.New("witness stack is empty")
|
||||
)
|
||||
|
||||
// rescanState indicates the progression of a registration before the notifier
|
||||
@ -671,15 +666,11 @@ func (n *TxNotifier) RegisterConf(txid *chainhash.Hash, pkScript []byte,
|
||||
// block along with the rest of the details. However not all
|
||||
// clients want the block, so we make a copy here w/o the block
|
||||
// if needed so we can give clients only what they ask for.
|
||||
confDetails := confSet.details
|
||||
if !ntfn.includeBlock && confDetails != nil {
|
||||
confDetailsCopy := *confDetails
|
||||
confDetailsCopy.Block = nil
|
||||
|
||||
confDetails = &confDetailsCopy
|
||||
if !ntfn.includeBlock && confSet.details != nil {
|
||||
confSet.details.Block = nil
|
||||
}
|
||||
|
||||
err := n.dispatchConfDetails(ntfn, confDetails)
|
||||
err := n.dispatchConfDetails(ntfn, confSet.details)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -772,8 +763,8 @@ func (n *TxNotifier) CancelConf(confRequest ConfRequest, confID uint64) {
|
||||
return
|
||||
}
|
||||
|
||||
Log.Debugf("Canceling confirmation notification: conf_id=%d, %v",
|
||||
confID, confRequest)
|
||||
Log.Infof("Canceling confirmation notification: conf_id=%d, %v", confID,
|
||||
confRequest)
|
||||
|
||||
// We'll close all the notification channels to let the client know
|
||||
// their cancel request has been fulfilled.
|
||||
@ -925,7 +916,7 @@ func (n *TxNotifier) dispatchConfDetails(
|
||||
// we'll dispatch a confirmation notification to the caller.
|
||||
confHeight := details.BlockHeight + ntfn.NumConfirmations - 1
|
||||
if confHeight <= n.currentHeight {
|
||||
Log.Debugf("Dispatching %v confirmation notification for %v",
|
||||
Log.Infof("Dispatching %v confirmation notification for %v",
|
||||
ntfn.NumConfirmations, ntfn.ConfRequest)
|
||||
|
||||
// We'll send a 0 value to the Updates channel,
|
||||
@ -1058,7 +1049,7 @@ func (n *TxNotifier) RegisterSpend(outpoint *wire.OutPoint, pkScript []byte,
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
Log.Debugf("New spend subscription: spend_id=%d, %v, height_hint=%d",
|
||||
Log.Infof("New spend subscription: spend_id=%d, %v, height_hint=%d",
|
||||
ntfn.SpendID, ntfn.SpendRequest, startHeight)
|
||||
|
||||
// Keep track of the notification request so that we can properly
|
||||
@ -1136,7 +1127,7 @@ func (n *TxNotifier) RegisterSpend(outpoint *wire.OutPoint, pkScript []byte,
|
||||
// notifications don't also attempt a historical dispatch.
|
||||
spendSet.rescanStatus = rescanPending
|
||||
|
||||
Log.Debugf("Dispatching historical spend rescan for %v, start=%d, "+
|
||||
Log.Infof("Dispatching historical spend rescan for %v, start=%d, "+
|
||||
"end=%d", ntfn.SpendRequest, startHeight, n.currentHeight)
|
||||
|
||||
return &SpendRegistration{
|
||||
@ -1171,7 +1162,7 @@ func (n *TxNotifier) CancelSpend(spendRequest SpendRequest, spendID uint64) {
|
||||
return
|
||||
}
|
||||
|
||||
Log.Debugf("Canceling spend notification: spend_id=%d, %v", spendID,
|
||||
Log.Infof("Canceling spend notification: spend_id=%d, %v", spendID,
|
||||
spendRequest)
|
||||
|
||||
// We'll close all the notification channels to let the client know
|
||||
@ -1302,19 +1293,6 @@ func (n *TxNotifier) updateSpendDetails(spendRequest SpendRequest,
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return an error if the witness data is not present in the spending
|
||||
// transaction.
|
||||
//
|
||||
// NOTE: if the witness stack is empty, we will do a critical log which
|
||||
// shuts down the node.
|
||||
if !details.HasSpenderWitness() {
|
||||
Log.Criticalf("Found spending tx for outpoint=%v, but the "+
|
||||
"transaction %v does not have witness",
|
||||
spendRequest.OutPoint, details.SpendingTx.TxHash())
|
||||
|
||||
return ErrEmptyWitnessStack
|
||||
}
|
||||
|
||||
// If the historical rescan found the spending transaction for this
|
||||
// request, but it's at a later height than the notifier (this can
|
||||
// happen due to latency with the backend during a reorg), then we'll
|
||||
@ -1364,7 +1342,7 @@ func (n *TxNotifier) dispatchSpendDetails(ntfn *SpendNtfn, details *SpendDetail)
|
||||
return nil
|
||||
}
|
||||
|
||||
Log.Debugf("Dispatching confirmed spend notification for %v at "+
|
||||
Log.Infof("Dispatching confirmed spend notification for %v at "+
|
||||
"current height=%d: %v", ntfn.SpendRequest, n.currentHeight,
|
||||
details)
|
||||
|
||||
@ -1743,7 +1721,7 @@ func (n *TxNotifier) NotifyHeight(height uint32) error {
|
||||
for ntfn := range n.ntfnsByConfirmHeight[height] {
|
||||
confSet := n.confNotifications[ntfn.ConfRequest]
|
||||
|
||||
Log.Debugf("Dispatching %v confirmation notification for %v",
|
||||
Log.Infof("Dispatching %v confirmation notification for %v",
|
||||
ntfn.NumConfirmations, ntfn.ConfRequest)
|
||||
|
||||
// The default notification we assigned above includes the
|
||||
|
||||
@ -34,8 +34,6 @@ var (
|
||||
0x86, 0xf4, 0xcb, 0xf9, 0x8e, 0xae, 0xd2, 0x21,
|
||||
0xb3, 0x0b, 0xd9, 0xa0, 0xb9, 0x28,
|
||||
}
|
||||
|
||||
testWitness = [][]byte{{0x01}}
|
||||
)
|
||||
|
||||
type mockHintCache struct {
|
||||
@ -749,7 +747,6 @@ func TestTxNotifierHistoricalSpendDispatch(t *testing.T) {
|
||||
spendTx := wire.NewMsgTx(2)
|
||||
spendTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: spentOutpoint,
|
||||
Witness: testWitness,
|
||||
SignatureScript: testSigScript,
|
||||
})
|
||||
spendTxHash := spendTx.TxHash()
|
||||
@ -815,7 +812,7 @@ func TestTxNotifierHistoricalSpendDispatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxNotifierMultipleHistoricalConfRescans ensures that we don't attempt to
|
||||
// TestTxNotifierMultipleHistoricalRescans ensures that we don't attempt to
|
||||
// request multiple historical confirmation rescans per transactions.
|
||||
func TestTxNotifierMultipleHistoricalConfRescans(t *testing.T) {
|
||||
t.Parallel()
|
||||
@ -862,7 +859,7 @@ func TestTxNotifierMultipleHistoricalConfRescans(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxNotifierMultipleHistoricalSpendRescans ensures that we don't attempt to
|
||||
// TestTxNotifierMultipleHistoricalRescans ensures that we don't attempt to
|
||||
// request multiple historical spend rescans per outpoints.
|
||||
func TestTxNotifierMultipleHistoricalSpendRescans(t *testing.T) {
|
||||
t.Parallel()
|
||||
@ -897,17 +894,10 @@ func TestTxNotifierMultipleHistoricalSpendRescans(t *testing.T) {
|
||||
// register another notification. We should also expect not to see a
|
||||
// historical rescan request since the confirmation details should be
|
||||
// cached.
|
||||
msgTx := &wire.MsgTx{
|
||||
TxIn: []*wire.TxIn{
|
||||
{PreviousOutPoint: op, Witness: testWitness},
|
||||
},
|
||||
TxOut: []*wire.TxOut{},
|
||||
}
|
||||
|
||||
spendDetails := &chainntnfs.SpendDetail{
|
||||
SpentOutPoint: &op,
|
||||
SpenderTxHash: &chainntnfs.ZeroHash,
|
||||
SpendingTx: msgTx,
|
||||
SpendingTx: wire.NewMsgTx(2),
|
||||
SpenderInputIndex: 0,
|
||||
SpendingHeight: startingHeight - 1,
|
||||
}
|
||||
@ -1031,17 +1021,10 @@ func TestTxNotifierMultipleHistoricalNtfns(t *testing.T) {
|
||||
// We'll assume a historical rescan was dispatched and found the
|
||||
// following spend details. We'll let the notifier know so that it can
|
||||
// stop watching at tip.
|
||||
msgTx := &wire.MsgTx{
|
||||
TxIn: []*wire.TxIn{
|
||||
{PreviousOutPoint: op, Witness: testWitness},
|
||||
},
|
||||
TxOut: []*wire.TxOut{},
|
||||
}
|
||||
|
||||
expectedSpendDetails := &chainntnfs.SpendDetail{
|
||||
SpentOutPoint: &op,
|
||||
SpenderTxHash: &chainntnfs.ZeroHash,
|
||||
SpendingTx: msgTx,
|
||||
SpendingTx: wire.NewMsgTx(2),
|
||||
SpenderInputIndex: 0,
|
||||
SpendingHeight: startingHeight - 1,
|
||||
}
|
||||
@ -1741,7 +1724,7 @@ func TestTxNotifierSpendReorg(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxNotifierSpendReorgMissed tests that a call to RegisterSpend after the
|
||||
// TestTxNotifierUpdateSpendReorg tests that a call to RegisterSpend after the
|
||||
// spend has been confirmed, and then UpdateSpendDetails (called by historical
|
||||
// dispatch), followed by a chain re-org will notify on the Reorg channel. This
|
||||
// was not always the case and has since been fixed.
|
||||
@ -1761,7 +1744,6 @@ func TestTxNotifierSpendReorgMissed(t *testing.T) {
|
||||
spendTx := wire.NewMsgTx(2)
|
||||
spendTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: op,
|
||||
Witness: testWitness,
|
||||
SignatureScript: testSigScript,
|
||||
})
|
||||
spendTxHash := spendTx.TxHash()
|
||||
@ -2160,7 +2142,7 @@ func TestTxNotifierSpendHintCache(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxNotifierSpendDuringHistoricalRescan checks that the height hints and
|
||||
// TestTxNotifierSpendHinthistoricalRescan checks that the height hints and
|
||||
// spend notifications behave as expected when a spend is found at tip during a
|
||||
// historical rescan.
|
||||
func TestTxNotifierSpendDuringHistoricalRescan(t *testing.T) {
|
||||
|
||||
25
chainreg/chaincode.go
Normal file
25
chainreg/chaincode.go
Normal file
@ -0,0 +1,25 @@
|
||||
package chainreg
|
||||
|
||||
// ChainCode is an enum-like structure for keeping track of the chains
|
||||
// currently supported within lnd.
|
||||
type ChainCode uint32
|
||||
|
||||
const (
|
||||
// BitcoinChain is Bitcoin's chain.
|
||||
BitcoinChain ChainCode = iota
|
||||
|
||||
// LitecoinChain is Litecoin's chain.
|
||||
LitecoinChain
|
||||
)
|
||||
|
||||
// String returns a string representation of the target ChainCode.
|
||||
func (c ChainCode) String() string {
|
||||
switch c {
|
||||
case BitcoinChain:
|
||||
return "bitcoin"
|
||||
case LitecoinChain:
|
||||
return "litecoin"
|
||||
default:
|
||||
return "kekcoin"
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,13 @@
|
||||
package chainreg
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
bitcoinCfg "github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
bitcoinWire "github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
litecoinCfg "github.com/ltcsuite/ltcd/chaincfg"
|
||||
litecoinWire "github.com/ltcsuite/ltcd/wire"
|
||||
)
|
||||
|
||||
// BitcoinNetParams couples the p2p parameters of a network with the
|
||||
@ -14,6 +18,14 @@ type BitcoinNetParams struct {
|
||||
CoinType uint32
|
||||
}
|
||||
|
||||
// LitecoinNetParams couples the p2p parameters of a network with the
|
||||
// corresponding RPC port of a daemon running on the particular network.
|
||||
type LitecoinNetParams struct {
|
||||
*litecoinCfg.Params
|
||||
RPCPort string
|
||||
CoinType uint32
|
||||
}
|
||||
|
||||
// BitcoinTestNetParams contains parameters specific to the 3rd version of the
|
||||
// test network.
|
||||
var BitcoinTestNetParams = BitcoinNetParams{
|
||||
@ -45,6 +57,38 @@ var BitcoinSigNetParams = BitcoinNetParams{
|
||||
CoinType: keychain.CoinTypeTestnet,
|
||||
}
|
||||
|
||||
// LitecoinSimNetParams contains parameters specific to the simulation test
|
||||
// network.
|
||||
var LitecoinSimNetParams = LitecoinNetParams{
|
||||
Params: &litecoinCfg.TestNet4Params,
|
||||
RPCPort: "18556",
|
||||
CoinType: keychain.CoinTypeTestnet,
|
||||
}
|
||||
|
||||
// LitecoinTestNetParams contains parameters specific to the 4th version of the
|
||||
// test network.
|
||||
var LitecoinTestNetParams = LitecoinNetParams{
|
||||
Params: &litecoinCfg.TestNet4Params,
|
||||
RPCPort: "19334",
|
||||
CoinType: keychain.CoinTypeTestnet,
|
||||
}
|
||||
|
||||
// LitecoinMainNetParams contains the parameters specific to the current
|
||||
// Litecoin mainnet.
|
||||
var LitecoinMainNetParams = LitecoinNetParams{
|
||||
Params: &litecoinCfg.MainNetParams,
|
||||
RPCPort: "9334",
|
||||
CoinType: keychain.CoinTypeLitecoin,
|
||||
}
|
||||
|
||||
// LitecoinRegTestNetParams contains parameters specific to a local litecoin
|
||||
// regtest network.
|
||||
var LitecoinRegTestNetParams = LitecoinNetParams{
|
||||
Params: &litecoinCfg.RegressionNetParams,
|
||||
RPCPort: "18334",
|
||||
CoinType: keychain.CoinTypeTestnet,
|
||||
}
|
||||
|
||||
// BitcoinRegTestNetParams contains parameters specific to a local bitcoin
|
||||
// regtest network.
|
||||
var BitcoinRegTestNetParams = BitcoinNetParams{
|
||||
@ -53,8 +97,56 @@ var BitcoinRegTestNetParams = BitcoinNetParams{
|
||||
CoinType: keychain.CoinTypeTestnet,
|
||||
}
|
||||
|
||||
// ApplyLitecoinParams applies the relevant chain configuration parameters that
|
||||
// differ for litecoin to the chain parameters typed for btcsuite derivation.
|
||||
// This function is used in place of using something like interface{} to
|
||||
// abstract over _which_ chain (or fork) the parameters are for.
|
||||
func ApplyLitecoinParams(params *BitcoinNetParams,
|
||||
litecoinParams *LitecoinNetParams) {
|
||||
|
||||
params.Name = litecoinParams.Name
|
||||
params.Net = bitcoinWire.BitcoinNet(litecoinParams.Net)
|
||||
params.DefaultPort = litecoinParams.DefaultPort
|
||||
params.CoinbaseMaturity = litecoinParams.CoinbaseMaturity
|
||||
|
||||
copy(params.GenesisHash[:], litecoinParams.GenesisHash[:])
|
||||
|
||||
// Address encoding magics
|
||||
params.PubKeyHashAddrID = litecoinParams.PubKeyHashAddrID
|
||||
params.ScriptHashAddrID = litecoinParams.ScriptHashAddrID
|
||||
params.PrivateKeyID = litecoinParams.PrivateKeyID
|
||||
params.WitnessPubKeyHashAddrID = litecoinParams.WitnessPubKeyHashAddrID
|
||||
params.WitnessScriptHashAddrID = litecoinParams.WitnessScriptHashAddrID
|
||||
params.Bech32HRPSegwit = litecoinParams.Bech32HRPSegwit
|
||||
|
||||
copy(params.HDPrivateKeyID[:], litecoinParams.HDPrivateKeyID[:])
|
||||
copy(params.HDPublicKeyID[:], litecoinParams.HDPublicKeyID[:])
|
||||
|
||||
params.HDCoinType = litecoinParams.HDCoinType
|
||||
|
||||
checkPoints := make([]chaincfg.Checkpoint, len(litecoinParams.Checkpoints))
|
||||
for i := 0; i < len(litecoinParams.Checkpoints); i++ {
|
||||
var chainHash chainhash.Hash
|
||||
copy(chainHash[:], litecoinParams.Checkpoints[i].Hash[:])
|
||||
|
||||
checkPoints[i] = chaincfg.Checkpoint{
|
||||
Height: litecoinParams.Checkpoints[i].Height,
|
||||
Hash: &chainHash,
|
||||
}
|
||||
}
|
||||
params.Checkpoints = checkPoints
|
||||
|
||||
params.RPCPort = litecoinParams.RPCPort
|
||||
params.CoinType = litecoinParams.CoinType
|
||||
}
|
||||
|
||||
// IsTestnet tests if the givern params correspond to a testnet
|
||||
// parameter configuration.
|
||||
func IsTestnet(params *BitcoinNetParams) bool {
|
||||
return params.Params.Net == bitcoinWire.TestNet3
|
||||
switch params.Params.Net {
|
||||
case bitcoinWire.TestNet3, bitcoinWire.BitcoinNet(litecoinWire.TestNet4):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,14 +5,16 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/btcsuite/btcwallet/chain"
|
||||
@ -23,8 +25,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
@ -42,6 +43,13 @@ type Config struct {
|
||||
// Bitcoin defines settings for the Bitcoin chain.
|
||||
Bitcoin *lncfg.Chain
|
||||
|
||||
// Litecoin defines settings for the Litecoin chain.
|
||||
Litecoin *lncfg.Chain
|
||||
|
||||
// PrimaryChain is a function that returns our primary chain via its
|
||||
// ChainCode.
|
||||
PrimaryChain func() ChainCode
|
||||
|
||||
// HeightHintCacheQueryDisable is a boolean that disables height hint
|
||||
// queries if true.
|
||||
HeightHintCacheQueryDisable bool
|
||||
@ -53,9 +61,15 @@ type Config struct {
|
||||
// BitcoindMode defines settings for connecting to a bitcoind node.
|
||||
BitcoindMode *lncfg.Bitcoind
|
||||
|
||||
// LitecoindMode defines settings for connecting to a litecoind node.
|
||||
LitecoindMode *lncfg.Bitcoind
|
||||
|
||||
// BtcdMode defines settings for connecting to a btcd node.
|
||||
BtcdMode *lncfg.Btcd
|
||||
|
||||
// LtcdMode defines settings for connecting to an ltcd node.
|
||||
LtcdMode *lncfg.Btcd
|
||||
|
||||
// HeightHintDB is a pointer to the database that stores the height
|
||||
// hints.
|
||||
HeightHintDB kvdb.Backend
|
||||
@ -64,14 +78,6 @@ type Config struct {
|
||||
// state.
|
||||
ChanStateDB *channeldb.ChannelStateDB
|
||||
|
||||
// AuxLeafStore is an optional store that can be used to store auxiliary
|
||||
// leaves for certain custom channel types.
|
||||
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
|
||||
|
||||
// AuxSigner is an optional signer that can be used to sign auxiliary
|
||||
// leaves for certain custom channel types.
|
||||
AuxSigner fn.Option[lnwallet.AuxSigner]
|
||||
|
||||
// BlockCache is the main cache for storing block information.
|
||||
BlockCache *blockcache.BlockCache
|
||||
|
||||
@ -86,13 +92,9 @@ type Config struct {
|
||||
// ActiveNetParams details the current chain we are on.
|
||||
ActiveNetParams BitcoinNetParams
|
||||
|
||||
// Deprecated: Use Fee.URL. FeeURL defines the URL for fee estimation
|
||||
// we will use. This field is optional.
|
||||
FeeURL string
|
||||
|
||||
// Fee defines settings for the web fee estimator. This field is
|
||||
// FeeURL defines the URL for fee estimation we will use. This field is
|
||||
// optional.
|
||||
Fee *lncfg.Fee
|
||||
FeeURL string
|
||||
|
||||
// Dialer is a function closure that will be used to establish outbound
|
||||
// TCP connections to Bitcoin peers in the event of a pruned block being
|
||||
@ -128,6 +130,13 @@ const (
|
||||
// delta.
|
||||
DefaultBitcoinTimeLockDelta = 80
|
||||
|
||||
DefaultLitecoinMinHTLCInMSat = lnwire.MilliSatoshi(1)
|
||||
DefaultLitecoinMinHTLCOutMSat = lnwire.MilliSatoshi(1000)
|
||||
DefaultLitecoinBaseFeeMSat = lnwire.MilliSatoshi(1000)
|
||||
DefaultLitecoinFeeRate = lnwire.MilliSatoshi(1)
|
||||
DefaultLitecoinTimeLockDelta = 576
|
||||
DefaultLitecoinDustLimit = btcutil.Amount(54600)
|
||||
|
||||
// DefaultBitcoinStaticFeePerKW is the fee rate of 50 sat/vbyte
|
||||
// expressed in sat/kw.
|
||||
DefaultBitcoinStaticFeePerKW = chainfee.SatPerKWeight(12500)
|
||||
@ -136,12 +145,22 @@ const (
|
||||
// static estimators.
|
||||
DefaultBitcoinStaticMinRelayFeeRate = chainfee.FeePerKwFloor
|
||||
|
||||
// DefaultMinOutboundPeers is the min number of connected
|
||||
// outbound peers the chain backend should have to maintain a
|
||||
// healthy connection to the network.
|
||||
DefaultMinOutboundPeers = 6
|
||||
// DefaultLitecoinStaticFeePerKW is the fee rate of 200 sat/vbyte
|
||||
// expressed in sat/kw.
|
||||
DefaultLitecoinStaticFeePerKW = chainfee.SatPerKWeight(50000)
|
||||
|
||||
// BtcToLtcConversionRate is a fixed ratio used in order to scale up
|
||||
// payments when running on the Litecoin chain.
|
||||
BtcToLtcConversionRate = 60
|
||||
)
|
||||
|
||||
// DefaultLtcChannelConstraints is the default set of channel constraints that
|
||||
// are meant to be used when initially funding a Litecoin channel.
|
||||
var DefaultLtcChannelConstraints = channeldb.ChannelConstraints{
|
||||
DustLimit: DefaultLitecoinDustLimit,
|
||||
MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
|
||||
}
|
||||
|
||||
// PartialChainControl contains all the primary interfaces of the chain control
|
||||
// that can be purely constructed from the global configuration. No wallet
|
||||
// instance is required for constructing this partial state.
|
||||
@ -163,10 +182,6 @@ type PartialChainControl struct {
|
||||
// interested in.
|
||||
ChainNotifier chainntnfs.ChainNotifier
|
||||
|
||||
// BestBlockTracker is used to maintain a view of the global
|
||||
// chain state that changes over time
|
||||
BestBlockTracker *chainntnfs.BestBlockTracker
|
||||
|
||||
// MempoolNotifier is used to watch for spending events happened in
|
||||
// mempool.
|
||||
MempoolNotifier chainntnfs.MempoolWatcher
|
||||
@ -180,10 +195,14 @@ type PartialChainControl struct {
|
||||
ChainSource chain.Interface
|
||||
|
||||
// RoutingPolicy is the routing policy we have decided to use.
|
||||
RoutingPolicy models.ForwardingPolicy
|
||||
RoutingPolicy htlcswitch.ForwardingPolicy
|
||||
|
||||
// MinHtlcIn is the minimum HTLC we will accept.
|
||||
MinHtlcIn lnwire.MilliSatoshi
|
||||
|
||||
// ChannelConstraints is the set of default constraints that will be
|
||||
// used for any incoming or outgoing channel reservation requests.
|
||||
ChannelConstraints channeldb.ChannelConstraints
|
||||
}
|
||||
|
||||
// ChainControl couples the three primary interfaces lnd utilizes for a
|
||||
@ -218,25 +237,64 @@ type ChainControl struct {
|
||||
Wallet *lnwallet.LightningWallet
|
||||
}
|
||||
|
||||
// GenDefaultBtcConstraints generates the default set of channel constraints
|
||||
// that are to be used when funding a Bitcoin channel.
|
||||
func GenDefaultBtcConstraints() channeldb.ChannelConstraints {
|
||||
// We use the dust limit for the maximally sized witness program with
|
||||
// a 40-byte data push.
|
||||
dustLimit := lnwallet.DustLimitForSize(input.UnknownWitnessSize)
|
||||
|
||||
return channeldb.ChannelConstraints{
|
||||
DustLimit: dustLimit,
|
||||
MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
|
||||
}
|
||||
}
|
||||
|
||||
// NewPartialChainControl creates a new partial chain control that contains all
|
||||
// the parts that can be purely constructed from the passed in global
|
||||
// configuration and doesn't need any wallet instance yet.
|
||||
//
|
||||
//nolint:lll
|
||||
func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
|
||||
// Set the RPC config from the "home" chain. Multi-chain isn't yet
|
||||
// active, so we'll restrict usage to a particular chain for now.
|
||||
homeChainConfig := cfg.Bitcoin
|
||||
if cfg.PrimaryChain() == LitecoinChain {
|
||||
homeChainConfig = cfg.Litecoin
|
||||
}
|
||||
log.Infof("Primary chain is set to: %v", cfg.PrimaryChain())
|
||||
|
||||
cc := &PartialChainControl{
|
||||
Cfg: cfg,
|
||||
RoutingPolicy: models.ForwardingPolicy{
|
||||
}
|
||||
|
||||
switch cfg.PrimaryChain() {
|
||||
case BitcoinChain:
|
||||
cc.RoutingPolicy = htlcswitch.ForwardingPolicy{
|
||||
MinHTLCOut: cfg.Bitcoin.MinHTLCOut,
|
||||
BaseFee: cfg.Bitcoin.BaseFee,
|
||||
FeeRate: cfg.Bitcoin.FeeRate,
|
||||
TimeLockDelta: cfg.Bitcoin.TimeLockDelta,
|
||||
},
|
||||
MinHtlcIn: cfg.Bitcoin.MinHTLCIn,
|
||||
FeeEstimator: chainfee.NewStaticEstimator(
|
||||
}
|
||||
cc.MinHtlcIn = cfg.Bitcoin.MinHTLCIn
|
||||
cc.FeeEstimator = chainfee.NewStaticEstimator(
|
||||
DefaultBitcoinStaticFeePerKW,
|
||||
DefaultBitcoinStaticMinRelayFeeRate,
|
||||
),
|
||||
)
|
||||
case LitecoinChain:
|
||||
cc.RoutingPolicy = htlcswitch.ForwardingPolicy{
|
||||
MinHTLCOut: cfg.Litecoin.MinHTLCOut,
|
||||
BaseFee: cfg.Litecoin.BaseFee,
|
||||
FeeRate: cfg.Litecoin.FeeRate,
|
||||
TimeLockDelta: cfg.Litecoin.TimeLockDelta,
|
||||
}
|
||||
cc.MinHtlcIn = cfg.Litecoin.MinHTLCIn
|
||||
cc.FeeEstimator = chainfee.NewStaticEstimator(
|
||||
DefaultLitecoinStaticFeePerKW, 0,
|
||||
)
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("default routing policy for chain "+
|
||||
"%v is unknown", cfg.PrimaryChain())
|
||||
}
|
||||
|
||||
var err error
|
||||
@ -256,20 +314,10 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
|
||||
"cache: %v", err)
|
||||
}
|
||||
|
||||
// Map the deprecated feeurl flag to fee.url.
|
||||
if cfg.FeeURL != "" {
|
||||
if cfg.Fee.URL != "" {
|
||||
return nil, nil, errors.New("fee.url and " +
|
||||
"feeurl are mutually exclusive")
|
||||
}
|
||||
|
||||
cfg.Fee.URL = cfg.FeeURL
|
||||
}
|
||||
|
||||
// If spv mode is active, then we'll be using a distinct set of
|
||||
// chainControl interfaces that interface directly with the p2p network
|
||||
// of the selected chain.
|
||||
switch cfg.Bitcoin.Node {
|
||||
switch homeChainConfig.Node {
|
||||
case "neutrino":
|
||||
// We'll create ChainNotifier and FilteredChainView instances,
|
||||
// along with the wallet's ChainSource, which are all backed by
|
||||
@ -284,6 +332,18 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Map the deprecated neutrino feeurl flag to the general fee
|
||||
// url.
|
||||
if cfg.NeutrinoMode.FeeURL != "" {
|
||||
if cfg.FeeURL != "" {
|
||||
return nil, nil, errors.New("feeurl and " +
|
||||
"neutrino.feeurl are mutually " +
|
||||
"exclusive")
|
||||
}
|
||||
|
||||
cfg.FeeURL = cfg.NeutrinoMode.FeeURL
|
||||
}
|
||||
|
||||
cc.ChainSource = chain.NewNeutrinoClient(
|
||||
cfg.ActiveNetParams.Params, cfg.NeutrinoCS,
|
||||
)
|
||||
@ -294,11 +354,16 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
|
||||
return err
|
||||
}
|
||||
|
||||
case "bitcoind":
|
||||
bitcoindMode := cfg.BitcoindMode
|
||||
|
||||
case "bitcoind", "litecoind":
|
||||
var bitcoindMode *lncfg.Bitcoind
|
||||
switch {
|
||||
case cfg.Bitcoin.Active:
|
||||
bitcoindMode = cfg.BitcoindMode
|
||||
case cfg.Litecoin.Active:
|
||||
bitcoindMode = cfg.LitecoindMode
|
||||
}
|
||||
// Otherwise, we'll be speaking directly via RPC and ZMQ to a
|
||||
// bitcoind node. If the specified host for the btcd RPC
|
||||
// bitcoind node. If the specified host for the btcd/ltcd RPC
|
||||
// server already has a port specified, then we use that
|
||||
// directly. Otherwise, we assume the default port according to
|
||||
// the selected chain parameters.
|
||||
@ -317,13 +382,18 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
|
||||
rpcPort -= 2
|
||||
bitcoindHost = fmt.Sprintf("%v:%d",
|
||||
bitcoindMode.RPCHost, rpcPort)
|
||||
if cfg.Bitcoin.RegTest || cfg.Bitcoin.SigNet {
|
||||
if (cfg.Bitcoin.Active &&
|
||||
(cfg.Bitcoin.RegTest || cfg.Bitcoin.SigNet)) ||
|
||||
(cfg.Litecoin.Active && cfg.Litecoin.RegTest) {
|
||||
|
||||
conn, err := net.Dial("tcp", bitcoindHost)
|
||||
if err != nil || conn == nil {
|
||||
switch {
|
||||
case cfg.Bitcoin.RegTest:
|
||||
case cfg.Bitcoin.Active && cfg.Bitcoin.RegTest:
|
||||
rpcPort = 18443
|
||||
case cfg.Bitcoin.SigNet:
|
||||
case cfg.Litecoin.Active && cfg.Litecoin.RegTest:
|
||||
rpcPort = 19443
|
||||
case cfg.Bitcoin.Active && cfg.Bitcoin.SigNet:
|
||||
rpcPort = 38332
|
||||
}
|
||||
bitcoindHost = fmt.Sprintf("%v:%d",
|
||||
@ -396,7 +466,7 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
|
||||
DisableTLS: true,
|
||||
HTTPPostMode: true,
|
||||
}
|
||||
if !cfg.Bitcoin.RegTest {
|
||||
if cfg.Bitcoin.Active && !cfg.Bitcoin.RegTest {
|
||||
log.Infof("Initializing bitcoind backed fee estimator "+
|
||||
"in %s mode", bitcoindMode.EstimateMode)
|
||||
|
||||
@ -412,6 +482,23 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else if cfg.Litecoin.Active && !cfg.Litecoin.RegTest {
|
||||
log.Infof("Initializing litecoind backed fee "+
|
||||
"estimator in %s mode",
|
||||
bitcoindMode.EstimateMode)
|
||||
|
||||
// Finally, we'll re-initialize the fee estimator, as
|
||||
// if we're using litecoind as a backend, then we can
|
||||
// use live fee estimates, rather than a statically
|
||||
// coded value.
|
||||
fallBackFeeRate := chainfee.SatPerKVByte(25 * 1000)
|
||||
cc.FeeEstimator, err = chainfee.NewBitcoindEstimator(
|
||||
*rpcConfig, bitcoindMode.EstimateMode,
|
||||
fallBackFeeRate.FeePerKWeight(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// We need to use some apis that are not exposed by btcwallet,
|
||||
@ -520,34 +607,24 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
|
||||
|
||||
cc.HealthCheck = func() error {
|
||||
_, err := chainConn.RawRequest(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// On local test networks we usually don't have multiple
|
||||
// chain backend peers, so we can skip
|
||||
// the checkOutboundPeers test.
|
||||
if cfg.Bitcoin.SimNet || cfg.Bitcoin.RegTest {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make sure the bitcoind chain backend maintains a
|
||||
// healthy connection to the network by checking the
|
||||
// number of outbound peers.
|
||||
return checkOutboundPeers(chainConn)
|
||||
return err
|
||||
}
|
||||
|
||||
case "btcd":
|
||||
case "btcd", "ltcd":
|
||||
// Otherwise, we'll be speaking directly via RPC to a node.
|
||||
//
|
||||
// So first we'll load btcd's TLS cert for the RPC
|
||||
// So first we'll load btcd/ltcd's TLS cert for the RPC
|
||||
// connection. If a raw cert was specified in the config, then
|
||||
// we'll set that directly. Otherwise, we attempt to read the
|
||||
// cert from the path specified in the config.
|
||||
var (
|
||||
rpcCert []byte
|
||||
var btcdMode *lncfg.Btcd
|
||||
switch {
|
||||
case cfg.Bitcoin.Active:
|
||||
btcdMode = cfg.BtcdMode
|
||||
)
|
||||
case cfg.Litecoin.Active:
|
||||
btcdMode = cfg.LtcdMode
|
||||
}
|
||||
var rpcCert []byte
|
||||
if btcdMode.RawRPCCert != "" {
|
||||
rpcCert, err = hex.DecodeString(btcdMode.RawRPCCert)
|
||||
if err != nil {
|
||||
@ -558,7 +635,7 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
rpcCert, err = io.ReadAll(certFile)
|
||||
rpcCert, err = ioutil.ReadAll(certFile)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -567,7 +644,7 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
|
||||
}
|
||||
}
|
||||
|
||||
// If the specified host for the btcd RPC server already
|
||||
// If the specified host for the btcd/ltcd RPC server already
|
||||
// has a port specified, then we use that directly. Otherwise,
|
||||
// we assume the default port according to the selected chain
|
||||
// parameters.
|
||||
@ -643,26 +720,14 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
|
||||
// Use a query for our best block as a health check.
|
||||
cc.HealthCheck = func() error {
|
||||
_, _, err := cc.ChainSource.GetBestBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// On local test networks we usually don't have multiple
|
||||
// chain backend peers, so we can skip
|
||||
// the checkOutboundPeers test.
|
||||
if cfg.Bitcoin.SimNet || cfg.Bitcoin.RegTest {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make sure the btcd chain backend maintains a
|
||||
// healthy connection to the network by checking the
|
||||
// number of outbound peers.
|
||||
return checkOutboundPeers(chainRPC.Client)
|
||||
return err
|
||||
}
|
||||
|
||||
// If we're not in simnet or regtest mode, then we'll attempt
|
||||
// to use a proper fee estimator for testnet.
|
||||
if !cfg.Bitcoin.SimNet && !cfg.Bitcoin.RegTest {
|
||||
if !cfg.Bitcoin.SimNet && !cfg.Litecoin.SimNet &&
|
||||
!cfg.Bitcoin.RegTest && !cfg.Litecoin.RegTest {
|
||||
|
||||
log.Info("Initializing btcd backed fee estimator")
|
||||
|
||||
// Finally, we'll re-initialize the fee estimator, as
|
||||
@ -695,44 +760,34 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
|
||||
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unknown node type: %s",
|
||||
cfg.Bitcoin.Node)
|
||||
homeChainConfig.Node)
|
||||
}
|
||||
|
||||
cc.BestBlockTracker =
|
||||
chainntnfs.NewBestBlockTracker(cc.ChainNotifier)
|
||||
|
||||
switch {
|
||||
// If the fee URL isn't set, and the user is running mainnet, then
|
||||
// we'll return an error to instruct them to set a proper fee
|
||||
// estimator.
|
||||
case cfg.Fee.URL == "" && cfg.Bitcoin.MainNet &&
|
||||
cfg.Bitcoin.Node == "neutrino":
|
||||
case cfg.FeeURL == "" && cfg.Bitcoin.MainNet &&
|
||||
homeChainConfig.Node == "neutrino":
|
||||
|
||||
return nil, nil, fmt.Errorf("--fee.url parameter required " +
|
||||
return nil, nil, fmt.Errorf("--feeurl parameter required " +
|
||||
"when running neutrino on mainnet")
|
||||
|
||||
// Override default fee estimator if an external service is specified.
|
||||
case cfg.Fee.URL != "":
|
||||
case cfg.FeeURL != "":
|
||||
// Do not cache fees on regtest to make it easier to execute
|
||||
// manual or automated test cases.
|
||||
cacheFees := !cfg.Bitcoin.RegTest
|
||||
|
||||
log.Infof("Using external fee estimator %v: cached=%v: "+
|
||||
"min update timeout=%v, max update timeout=%v",
|
||||
cfg.Fee.URL, cacheFees, cfg.Fee.MinUpdateTimeout,
|
||||
cfg.Fee.MaxUpdateTimeout)
|
||||
log.Infof("Using external fee estimator %v: cached=%v",
|
||||
cfg.FeeURL, cacheFees)
|
||||
|
||||
cc.FeeEstimator, err = chainfee.NewWebAPIEstimator(
|
||||
cc.FeeEstimator = chainfee.NewWebAPIEstimator(
|
||||
chainfee.SparseConfFeeSource{
|
||||
URL: cfg.Fee.URL,
|
||||
URL: cfg.FeeURL,
|
||||
},
|
||||
!cacheFees,
|
||||
cfg.Fee.MinUpdateTimeout,
|
||||
cfg.Fee.MaxUpdateTimeout,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
ccCleanup := func() {
|
||||
@ -749,6 +804,12 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Select the default channel constraints for the primary chain.
|
||||
cc.ChannelConstraints = GenDefaultBtcConstraints()
|
||||
if cfg.PrimaryChain() == LitecoinChain {
|
||||
cc.ChannelConstraints = DefaultLtcChannelConstraints
|
||||
}
|
||||
|
||||
return cc, ccCleanup, nil
|
||||
}
|
||||
|
||||
@ -781,11 +842,11 @@ func NewChainControl(walletConfig lnwallet.Config,
|
||||
|
||||
lnWallet, err := lnwallet.NewLightningWallet(walletConfig)
|
||||
if err != nil {
|
||||
return nil, ccCleanup, fmt.Errorf("unable to create wallet: %w",
|
||||
return nil, ccCleanup, fmt.Errorf("unable to create wallet: %v",
|
||||
err)
|
||||
}
|
||||
if err := lnWallet.Startup(); err != nil {
|
||||
return nil, ccCleanup, fmt.Errorf("unable to create wallet: %w",
|
||||
return nil, ccCleanup, fmt.Errorf("unable to create wallet: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
@ -854,6 +915,33 @@ var (
|
||||
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
})
|
||||
|
||||
// LitecoinTestnetGenesis is the genesis hash of Litecoin's testnet4
|
||||
// chain.
|
||||
LitecoinTestnetGenesis = chainhash.Hash([chainhash.HashSize]byte{
|
||||
0xa0, 0x29, 0x3e, 0x4e, 0xeb, 0x3d, 0xa6, 0xe6,
|
||||
0xf5, 0x6f, 0x81, 0xed, 0x59, 0x5f, 0x57, 0x88,
|
||||
0x0d, 0x1a, 0x21, 0x56, 0x9e, 0x13, 0xee, 0xfd,
|
||||
0xd9, 0x51, 0x28, 0x4b, 0x5a, 0x62, 0x66, 0x49,
|
||||
})
|
||||
|
||||
// LitecoinMainnetGenesis is the genesis hash of Litecoin's main chain.
|
||||
LitecoinMainnetGenesis = chainhash.Hash([chainhash.HashSize]byte{
|
||||
0xe2, 0xbf, 0x04, 0x7e, 0x7e, 0x5a, 0x19, 0x1a,
|
||||
0xa4, 0xef, 0x34, 0xd3, 0x14, 0x97, 0x9d, 0xc9,
|
||||
0x98, 0x6e, 0x0f, 0x19, 0x25, 0x1e, 0xda, 0xba,
|
||||
0x59, 0x40, 0xfd, 0x1f, 0xe3, 0x65, 0xa7, 0x12,
|
||||
})
|
||||
|
||||
// chainMap is a simple index that maps a chain's genesis hash to the
|
||||
// ChainCode enum for that chain.
|
||||
chainMap = map[chainhash.Hash]ChainCode{
|
||||
BitcoinTestnetGenesis: BitcoinChain,
|
||||
LitecoinTestnetGenesis: LitecoinChain,
|
||||
|
||||
BitcoinMainnetGenesis: BitcoinChain,
|
||||
LitecoinMainnetGenesis: LitecoinChain,
|
||||
}
|
||||
|
||||
// ChainDNSSeeds is a map of a chain's hash to the set of DNS seeds
|
||||
// that will be use to bootstrap peers upon first startup.
|
||||
//
|
||||
@ -889,33 +977,107 @@ var (
|
||||
"ln.signet.secp.tech",
|
||||
},
|
||||
},
|
||||
|
||||
LitecoinMainnetGenesis: {
|
||||
{
|
||||
"ltc.nodes.lightning.directory",
|
||||
"soa.nodes.lightning.directory",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// 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
|
||||
// maintains a healthy connection to the network.
|
||||
func checkOutboundPeers(client *rpcclient.Client) error {
|
||||
peers, err := client.GetPeerInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// ChainRegistry keeps track of the current chains.
|
||||
type ChainRegistry struct {
|
||||
sync.RWMutex
|
||||
|
||||
var outboundPeers int
|
||||
for _, peer := range peers {
|
||||
if !peer.Inbound {
|
||||
outboundPeers++
|
||||
}
|
||||
}
|
||||
activeChains map[ChainCode]*ChainControl
|
||||
netParams map[ChainCode]*BitcoinNetParams
|
||||
|
||||
if outboundPeers < DefaultMinOutboundPeers {
|
||||
log.Warnf("The chain backend has an insufficient number "+
|
||||
"of connected outbound peers (%d connected, expected "+
|
||||
"minimum is %d) which can be a security issue. "+
|
||||
"Connect to more trusted nodes manually if necessary.",
|
||||
outboundPeers, DefaultMinOutboundPeers)
|
||||
}
|
||||
|
||||
return nil
|
||||
primaryChain ChainCode
|
||||
}
|
||||
|
||||
// NewChainRegistry creates a new ChainRegistry.
|
||||
func NewChainRegistry() *ChainRegistry {
|
||||
return &ChainRegistry{
|
||||
activeChains: make(map[ChainCode]*ChainControl),
|
||||
netParams: make(map[ChainCode]*BitcoinNetParams),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterChain assigns an active ChainControl instance to a target chain
|
||||
// identified by its ChainCode.
|
||||
func (c *ChainRegistry) RegisterChain(newChain ChainCode,
|
||||
cc *ChainControl) {
|
||||
|
||||
c.Lock()
|
||||
c.activeChains[newChain] = cc
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
// LookupChain attempts to lookup an active ChainControl instance for the
|
||||
// target chain.
|
||||
func (c *ChainRegistry) LookupChain(targetChain ChainCode) (
|
||||
*ChainControl, bool) {
|
||||
|
||||
c.RLock()
|
||||
cc, ok := c.activeChains[targetChain]
|
||||
c.RUnlock()
|
||||
return cc, ok
|
||||
}
|
||||
|
||||
// LookupChainByHash attempts to look up an active ChainControl which
|
||||
// corresponds to the passed genesis hash.
|
||||
func (c *ChainRegistry) LookupChainByHash(
|
||||
chainHash chainhash.Hash) (*ChainControl, bool) {
|
||||
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
targetChain, ok := chainMap[chainHash]
|
||||
if !ok {
|
||||
return nil, ok
|
||||
}
|
||||
|
||||
cc, ok := c.activeChains[targetChain]
|
||||
return cc, ok
|
||||
}
|
||||
|
||||
// RegisterPrimaryChain sets a target chain as the "home chain" for lnd.
|
||||
func (c *ChainRegistry) RegisterPrimaryChain(cc ChainCode) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.primaryChain = cc
|
||||
}
|
||||
|
||||
// PrimaryChain returns the primary chain for this running lnd instance. The
|
||||
// primary chain is considered the "home base" while the other registered
|
||||
// chains are treated as secondary chains.
|
||||
func (c *ChainRegistry) PrimaryChain() ChainCode {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
return c.primaryChain
|
||||
}
|
||||
|
||||
// ActiveChains returns a slice containing the active chains.
|
||||
func (c *ChainRegistry) ActiveChains() []ChainCode {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
chains := make([]ChainCode, 0, len(c.activeChains))
|
||||
for activeChain := range c.activeChains {
|
||||
chains = append(chains, activeChain)
|
||||
}
|
||||
|
||||
return chains
|
||||
}
|
||||
|
||||
// NumActiveChains returns the total number of active chains.
|
||||
func (c *ChainRegistry) NumActiveChains() uint32 {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
return uint32(len(c.activeChains))
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
@ -214,14 +213,4 @@ func (n *NoChainSource) BackEnd() string {
|
||||
return noChainBackendName
|
||||
}
|
||||
|
||||
func (n *NoChainSource) TestMempoolAccept([]*wire.MsgTx,
|
||||
float64) ([]*btcjson.TestMempoolAcceptResult, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n *NoChainSource) MapRPCErr(err error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var _ chain.Interface = (*NoChainSource)(nil)
|
||||
|
||||
@ -249,7 +249,6 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error,
|
||||
acceptRequests := make(map[[32]byte]*chanAcceptInfo)
|
||||
|
||||
for {
|
||||
//nolint:lll
|
||||
select {
|
||||
// Consume requests passed to us from our Accept() function and
|
||||
// send them into our stream.
|
||||
@ -332,54 +331,6 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error,
|
||||
):
|
||||
commitmentType = lnrpc.CommitmentType_ANCHORS
|
||||
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.SimpleTaprootChannelsRequiredStaging,
|
||||
lnwire.ZeroConfRequired,
|
||||
lnwire.ScidAliasRequired,
|
||||
):
|
||||
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
|
||||
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.SimpleTaprootChannelsRequiredStaging,
|
||||
lnwire.ZeroConfRequired,
|
||||
):
|
||||
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
|
||||
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.SimpleTaprootChannelsRequiredStaging,
|
||||
lnwire.ScidAliasRequired,
|
||||
):
|
||||
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
|
||||
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.SimpleTaprootChannelsRequiredStaging,
|
||||
):
|
||||
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
|
||||
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.SimpleTaprootOverlayChansRequired,
|
||||
lnwire.ZeroConfRequired,
|
||||
lnwire.ScidAliasRequired,
|
||||
):
|
||||
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY
|
||||
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.SimpleTaprootOverlayChansRequired,
|
||||
lnwire.ZeroConfRequired,
|
||||
):
|
||||
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY
|
||||
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.SimpleTaprootOverlayChansRequired,
|
||||
lnwire.ScidAliasRequired,
|
||||
):
|
||||
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY
|
||||
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.SimpleTaprootOverlayChansRequired,
|
||||
):
|
||||
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY
|
||||
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
):
|
||||
|
||||
@ -72,7 +72,7 @@ func FetchBackupForChan(chanPoint wire.OutPoint, chanSource LiveChannelSource,
|
||||
// the source to obtain any extra information that we may need.
|
||||
staticChanBackup, err := assembleChanBackup(addrSource, targetChan)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create chan backup: %w", err)
|
||||
return nil, fmt.Errorf("unable to create chan backup: %v", err)
|
||||
}
|
||||
|
||||
return staticChanBackup, nil
|
||||
|
||||
@ -2,6 +2,7 @@ package chanbackup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@ -92,18 +93,17 @@ func (b *MultiFile) UpdateAndSwap(newBackup PackedMulti) error {
|
||||
var err error
|
||||
b.tempFile, err = os.Create(b.tempFileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create temp file: %w", err)
|
||||
return fmt.Errorf("unable to create temp file: %v", err)
|
||||
}
|
||||
|
||||
// With the file created, we'll write the new packed multi backup and
|
||||
// remove the temporary file all together once this method exits.
|
||||
_, err = b.tempFile.Write([]byte(newBackup))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to write backup to temp file: %w",
|
||||
err)
|
||||
return fmt.Errorf("unable to write backup to temp file: %v", err)
|
||||
}
|
||||
if err := b.tempFile.Sync(); err != nil {
|
||||
return fmt.Errorf("unable to sync temp file: %w", err)
|
||||
return fmt.Errorf("unable to sync temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(b.tempFileName)
|
||||
|
||||
@ -114,7 +114,7 @@ func (b *MultiFile) UpdateAndSwap(newBackup PackedMulti) error {
|
||||
// sure to close the current file as some OSes don't support
|
||||
// renaming a file that's already open (Windows).
|
||||
if err := b.tempFile.Close(); err != nil {
|
||||
return fmt.Errorf("unable to close file: %w", err)
|
||||
return fmt.Errorf("unable to close file: %v", err)
|
||||
}
|
||||
|
||||
// Finally, we'll attempt to atomically rename the temporary file to
|
||||
@ -137,7 +137,7 @@ func (b *MultiFile) ExtractMulti(keyChain keychain.KeyRing) (*Multi, error) {
|
||||
// Now that we've confirmed the target file is populated, we'll read
|
||||
// all the contents of the file. This function ensures that file is
|
||||
// always closed, even if we can't read the contents.
|
||||
multiBytes, err := os.ReadFile(b.fileName)
|
||||
multiBytes, err := ioutil.ReadFile(b.fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package chanbackup
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -15,7 +16,7 @@ import (
|
||||
func makeFakePackedMulti() (PackedMulti, error) {
|
||||
newPackedMulti := make([]byte, 50)
|
||||
if _, err := rand.Read(newPackedMulti[:]); err != nil {
|
||||
return nil, fmt.Errorf("unable to make test backup: %w", err)
|
||||
return nil, fmt.Errorf("unable to make test backup: %v", err)
|
||||
}
|
||||
|
||||
return PackedMulti(newPackedMulti), nil
|
||||
@ -26,7 +27,7 @@ func assertBackupMatches(t *testing.T, filePath string,
|
||||
|
||||
t.Helper()
|
||||
|
||||
packedBackup, err := os.ReadFile(filePath)
|
||||
packedBackup, err := ioutil.ReadFile(filePath)
|
||||
require.NoError(t, err, "unable to test file")
|
||||
|
||||
if !bytes.Equal(packedBackup, currentBackup) {
|
||||
|
||||
@ -27,3 +27,19 @@ func DisableLog() {
|
||||
func UseLogger(logger btclog.Logger) {
|
||||
log = logger
|
||||
}
|
||||
|
||||
// logClosure is used to provide a closure over expensive logging operations so
|
||||
// don't have to be performed when the logging level doesn't warrant it.
|
||||
type logClosure func() string
|
||||
|
||||
// String invokes the underlying function and returns the result.
|
||||
func (c logClosure) String() string {
|
||||
return c()
|
||||
}
|
||||
|
||||
// newLogClosure returns a new closure over a function that returns a string
|
||||
// which itself provides a Stringer interface so that it can be used with the
|
||||
// logging system.
|
||||
func newLogClosure(c func() string) logClosure {
|
||||
return logClosure(c)
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ func (m Multi) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error {
|
||||
// directly to the passed writer.
|
||||
e, err := lnencrypt.KeyRingEncrypter(keyRing)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to generate encrypt key %w", err)
|
||||
return fmt.Errorf("unable to generate encrypt key %v", err)
|
||||
}
|
||||
|
||||
return e.EncryptPayloadToWriter(multiBackupBuffer.Bytes(), w)
|
||||
@ -107,7 +107,7 @@ func (m *Multi) UnpackFromReader(r io.Reader, keyRing keychain.KeyRing) error {
|
||||
// encryption keys.
|
||||
e, err := lnencrypt.KeyRingEncrypter(keyRing)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to generate encrypt key %w", err)
|
||||
return fmt.Errorf("unable to generate encrypt key %v", err)
|
||||
}
|
||||
plaintextBackup, err := e.DecryptPayloadFromReader(r)
|
||||
if err != nil {
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnutils"
|
||||
)
|
||||
|
||||
// Swapper is an interface that allows the chanbackup.SubSwapper to update the
|
||||
@ -159,8 +158,7 @@ func (s *SubSwapper) Start() error {
|
||||
// Stop signals the SubSwapper to being a graceful shutdown.
|
||||
func (s *SubSwapper) Stop() error {
|
||||
s.stopped.Do(func() {
|
||||
log.Infof("chanbackup.SubSwapper shutting down...")
|
||||
defer log.Debug("chanbackup.SubSwapper shutdown complete")
|
||||
log.Infof("Stopping chanbackup.SubSwapper")
|
||||
|
||||
close(s.quit)
|
||||
s.wg.Wait()
|
||||
@ -227,7 +225,7 @@ func (s *SubSwapper) updateBackupFile(closedChans ...wire.OutPoint) error {
|
||||
var b bytes.Buffer
|
||||
err = newMulti.PackToWriter(&b, s.keyRing)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to pack multi backup: %w", err)
|
||||
return fmt.Errorf("unable to pack multi backup: %v", err)
|
||||
}
|
||||
|
||||
// Finally, we'll swap out the old backup for this new one in a single
|
||||
@ -235,7 +233,7 @@ func (s *SubSwapper) updateBackupFile(closedChans ...wire.OutPoint) error {
|
||||
// channels.
|
||||
err = s.Swapper.UpdateAndSwap(PackedMulti(b.Bytes()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update multi backup: %w", err)
|
||||
return fmt.Errorf("unable to update multi backup: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -279,8 +277,9 @@ func (s *SubSwapper) backupUpdater() {
|
||||
)
|
||||
for i, closedChan := range chanUpdate.ClosedChans {
|
||||
log.Debugf("Removing channel %v from backup "+
|
||||
"state", lnutils.NewLogClosure(
|
||||
chanUpdate.ClosedChans[i].String))
|
||||
"state", newLogClosure(func() string {
|
||||
return chanUpdate.ClosedChans[i].String()
|
||||
}))
|
||||
|
||||
delete(s.backupState, closedChan)
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ func (m *mockSwapper) UpdateAndSwap(newBackup PackedMulti) error {
|
||||
|
||||
swapState, err := newBackup.Unpack(m.keyChain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to decode on disk swaps: %w", err)
|
||||
return fmt.Errorf("unable to decode on disk swaps: %v", err)
|
||||
}
|
||||
|
||||
m.swapState = swapState
|
||||
|
||||
@ -4,9 +4,9 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnutils"
|
||||
)
|
||||
|
||||
// ChannelRestorer is an interface that allows the Recover method to map the
|
||||
@ -63,8 +63,9 @@ func Recover(backups []Single, restorer ChannelRestorer,
|
||||
log.Infof("Attempting to connect to node=%x (addrs=%v) to "+
|
||||
"restore ChannelPoint(%v)",
|
||||
backup.RemoteNodePub.SerializeCompressed(),
|
||||
lnutils.SpewLogClosure(backups[i].Addresses),
|
||||
backup.FundingOutpoint)
|
||||
newLogClosure(func() string {
|
||||
return spew.Sdump(backups[i].Addresses)
|
||||
}), backup.FundingOutpoint)
|
||||
|
||||
err = peerConnector.ConnectPeer(
|
||||
backup.RemoteNodePub, backup.Addresses,
|
||||
|
||||
@ -48,10 +48,6 @@ const (
|
||||
// commitment and HTLC outputs that pay directly to the channel
|
||||
// initiator.
|
||||
ScriptEnforcedLeaseVersion = 4
|
||||
|
||||
// SimpleTaprootVersion is a version that denotes this channel is using
|
||||
// the musig2 based taproot commitment format.
|
||||
SimpleTaprootVersion = 5
|
||||
)
|
||||
|
||||
// Single is a static description of an existing channel that can be used for
|
||||
@ -217,9 +213,6 @@ func NewSingle(channel *channeldb.OpenChannel,
|
||||
}
|
||||
|
||||
switch {
|
||||
case channel.ChanType.IsTaproot():
|
||||
single.Version = SimpleTaprootVersion
|
||||
|
||||
case channel.ChanType.HasLeaseExpiration():
|
||||
single.Version = ScriptEnforcedLeaseVersion
|
||||
single.LeaseExpiry = channel.ThawHeight
|
||||
@ -251,7 +244,6 @@ func (s *Single) Serialize(w io.Writer) error {
|
||||
case AnchorsCommitVersion:
|
||||
case AnchorsZeroFeeHtlcTxCommitVersion:
|
||||
case ScriptEnforcedLeaseVersion:
|
||||
case SimpleTaprootVersion:
|
||||
default:
|
||||
return fmt.Errorf("unable to serialize w/ unknown "+
|
||||
"version: %v", s.Version)
|
||||
@ -362,7 +354,7 @@ func (s *Single) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error {
|
||||
// nonce that we used to the passed io.Reader.
|
||||
e, err := lnencrypt.KeyRingEncrypter(keyRing)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to generate encrypt key %w", err)
|
||||
return fmt.Errorf("unable to generate encrypt key %v", err)
|
||||
}
|
||||
return e.EncryptPayloadToWriter(rawBytes.Bytes(), w)
|
||||
}
|
||||
@ -428,7 +420,6 @@ func (s *Single) Deserialize(r io.Reader) error {
|
||||
case AnchorsCommitVersion:
|
||||
case AnchorsZeroFeeHtlcTxCommitVersion:
|
||||
case ScriptEnforcedLeaseVersion:
|
||||
case SimpleTaprootVersion:
|
||||
default:
|
||||
return fmt.Errorf("unable to de-serialize w/ unknown "+
|
||||
"version: %v", s.Version)
|
||||
@ -544,7 +535,7 @@ func (s *Single) Deserialize(r io.Reader) error {
|
||||
func (s *Single) UnpackFromReader(r io.Reader, keyRing keychain.KeyRing) error {
|
||||
e, err := lnencrypt.KeyRingEncrypter(keyRing)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to generate key decrypter %w", err)
|
||||
return fmt.Errorf("unable to generate key decrypter %v", err)
|
||||
}
|
||||
plaintext, err := e.DecryptPayloadFromReader(r)
|
||||
if err != nil {
|
||||
|
||||
@ -126,64 +126,6 @@ func genRandomOpenChannelShell() (*channeldb.OpenChannel, error) {
|
||||
|
||||
chanType := channeldb.ChannelType(rand.Intn(8))
|
||||
|
||||
localCfg := channeldb.ChannelConfig{
|
||||
ChannelStateBounds: channeldb.ChannelStateBounds{},
|
||||
CommitmentParams: channeldb.CommitmentParams{
|
||||
CsvDelay: uint16(rand.Int63()),
|
||||
},
|
||||
MultiSigKey: keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(rand.Int63()),
|
||||
Index: uint32(rand.Int63()),
|
||||
},
|
||||
},
|
||||
RevocationBasePoint: keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(rand.Int63()),
|
||||
Index: uint32(rand.Int63()),
|
||||
},
|
||||
},
|
||||
PaymentBasePoint: keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(rand.Int63()),
|
||||
Index: uint32(rand.Int63()),
|
||||
},
|
||||
},
|
||||
DelayBasePoint: keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(rand.Int63()),
|
||||
Index: uint32(rand.Int63()),
|
||||
},
|
||||
},
|
||||
HtlcBasePoint: keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(rand.Int63()),
|
||||
Index: uint32(rand.Int63()),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
remoteCfg := channeldb.ChannelConfig{
|
||||
CommitmentParams: channeldb.CommitmentParams{
|
||||
CsvDelay: uint16(rand.Int63()),
|
||||
},
|
||||
MultiSigKey: keychain.KeyDescriptor{
|
||||
PubKey: pub,
|
||||
},
|
||||
RevocationBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: pub,
|
||||
},
|
||||
PaymentBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: pub,
|
||||
},
|
||||
DelayBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: pub,
|
||||
},
|
||||
HtlcBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: pub,
|
||||
},
|
||||
}
|
||||
|
||||
return &channeldb.OpenChannel{
|
||||
ChainHash: chainHash,
|
||||
ChanType: chanType,
|
||||
@ -192,10 +134,63 @@ func genRandomOpenChannelShell() (*channeldb.OpenChannel, error) {
|
||||
ShortChannelID: lnwire.NewShortChanIDFromInt(
|
||||
uint64(rand.Int63()),
|
||||
),
|
||||
ThawHeight: rand.Uint32(),
|
||||
IdentityPub: pub,
|
||||
LocalChanCfg: localCfg,
|
||||
RemoteChanCfg: remoteCfg,
|
||||
ThawHeight: rand.Uint32(),
|
||||
IdentityPub: pub,
|
||||
LocalChanCfg: channeldb.ChannelConfig{
|
||||
ChannelConstraints: channeldb.ChannelConstraints{
|
||||
CsvDelay: uint16(rand.Int63()),
|
||||
},
|
||||
MultiSigKey: keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(rand.Int63()),
|
||||
Index: uint32(rand.Int63()),
|
||||
},
|
||||
},
|
||||
RevocationBasePoint: keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(rand.Int63()),
|
||||
Index: uint32(rand.Int63()),
|
||||
},
|
||||
},
|
||||
PaymentBasePoint: keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(rand.Int63()),
|
||||
Index: uint32(rand.Int63()),
|
||||
},
|
||||
},
|
||||
DelayBasePoint: keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(rand.Int63()),
|
||||
Index: uint32(rand.Int63()),
|
||||
},
|
||||
},
|
||||
HtlcBasePoint: keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(rand.Int63()),
|
||||
Index: uint32(rand.Int63()),
|
||||
},
|
||||
},
|
||||
},
|
||||
RemoteChanCfg: channeldb.ChannelConfig{
|
||||
ChannelConstraints: channeldb.ChannelConstraints{
|
||||
CsvDelay: uint16(rand.Int63()),
|
||||
},
|
||||
MultiSigKey: keychain.KeyDescriptor{
|
||||
PubKey: pub,
|
||||
},
|
||||
RevocationBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: pub,
|
||||
},
|
||||
PaymentBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: pub,
|
||||
},
|
||||
DelayBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: pub,
|
||||
},
|
||||
HtlcBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: pub,
|
||||
},
|
||||
},
|
||||
RevocationProducer: shaChainProducer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -12,9 +12,7 @@ package chanfitness
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
@ -50,9 +48,6 @@ var (
|
||||
// ChannelEventStore maintains a set of event logs for the node's channels to
|
||||
// provide insight into the performance and health of channels.
|
||||
type ChannelEventStore struct {
|
||||
started atomic.Bool
|
||||
stopped atomic.Bool
|
||||
|
||||
cfg *Config
|
||||
|
||||
// peers tracks all of our currently monitored peers and their channels.
|
||||
@ -90,7 +85,7 @@ type Config struct {
|
||||
// for ease of testing.
|
||||
Clock clock.Clock
|
||||
|
||||
// WriteFlapCount records the flap count for a set of peers on disk.
|
||||
// WriteFlapCounts records the flap count for a set of peers on disk.
|
||||
WriteFlapCount func(map[route.Vertex]*channeldb.FlapCount) error
|
||||
|
||||
// ReadFlapCount gets the flap count for a peer on disk.
|
||||
@ -147,11 +142,7 @@ func NewChannelEventStore(config *Config) *ChannelEventStore {
|
||||
// information from the store. If this function fails, it cancels its existing
|
||||
// subscriptions and returns an error.
|
||||
func (c *ChannelEventStore) Start() error {
|
||||
log.Info("ChannelEventStore starting...")
|
||||
|
||||
if c.started.Swap(true) {
|
||||
return fmt.Errorf("ChannelEventStore started more than once")
|
||||
}
|
||||
log.Info("ChannelEventStore starting")
|
||||
|
||||
// Create a subscription to channel events.
|
||||
channelClient, err := c.cfg.SubscribeChannelEvents()
|
||||
@ -207,18 +198,12 @@ func (c *ChannelEventStore) Start() error {
|
||||
cancel: cancel,
|
||||
})
|
||||
|
||||
log.Debug("ChannelEventStore started")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop terminates all goroutines started by the event store.
|
||||
func (c *ChannelEventStore) Stop() error {
|
||||
log.Info("ChannelEventStore shutting down...")
|
||||
|
||||
if c.stopped.Swap(true) {
|
||||
return fmt.Errorf("ChannelEventStore stopped more than once")
|
||||
}
|
||||
func (c *ChannelEventStore) Stop() {
|
||||
log.Info("Stopping event store")
|
||||
|
||||
// Stop the consume goroutine.
|
||||
close(c.quit)
|
||||
@ -226,17 +211,7 @@ func (c *ChannelEventStore) Stop() error {
|
||||
|
||||
// Stop the ticker after the goroutine reading from it has exited, to
|
||||
// avoid a race.
|
||||
var err error
|
||||
if c.cfg.FlapCountTicker == nil {
|
||||
err = fmt.Errorf("ChannelEventStore FlapCountTicker not " +
|
||||
"initialized")
|
||||
} else {
|
||||
c.cfg.FlapCountTicker.Stop()
|
||||
}
|
||||
|
||||
log.Debugf("ChannelEventStore shutdown complete")
|
||||
|
||||
return err
|
||||
c.cfg.FlapCountTicker.Stop()
|
||||
}
|
||||
|
||||
// addChannel checks whether we are already tracking a channel's peer, creates a
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -3,8 +3,6 @@ package channeldb
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
)
|
||||
|
||||
// TestChannelCache checks the behavior of the channelCache with respect to
|
||||
@ -100,7 +98,7 @@ func assertHasChanEntries(t *testing.T, c *channelCache, start, end uint64) {
|
||||
// channelForInt generates a unique ChannelEdge given an integer.
|
||||
func channelForInt(i uint64) ChannelEdge {
|
||||
return ChannelEdge{
|
||||
Info: &models.ChannelEdgeInfo{
|
||||
Info: &ChannelEdgeInfo{
|
||||
ChannelID: i,
|
||||
},
|
||||
}
|
||||
|
||||
@ -17,15 +17,11 @@ import (
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lnmock"
|
||||
"github.com/lightningnetwork/lnd/lntest/channels"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/shachain"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -174,7 +170,7 @@ func fundingPointOption(chanPoint wire.OutPoint) testChannelOption {
|
||||
}
|
||||
|
||||
// channelIDOption is an option which sets the short channel ID of the channel.
|
||||
func channelIDOption(chanID lnwire.ShortChannelID) testChannelOption {
|
||||
var channelIDOption = func(chanID lnwire.ShortChannelID) testChannelOption {
|
||||
return func(params *testChannelParams) {
|
||||
params.channel.ShortChannelID = chanID
|
||||
}
|
||||
@ -236,21 +232,15 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
||||
}
|
||||
}
|
||||
|
||||
localStateBounds := ChannelStateBounds{
|
||||
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
|
||||
ChanReserve: btcutil.Amount(rand.Int63()),
|
||||
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
|
||||
MaxAcceptedHtlcs: uint16(rand.Int31()),
|
||||
}
|
||||
|
||||
localRenderingParams := CommitmentParams{
|
||||
DustLimit: btcutil.Amount(rand.Int63()),
|
||||
CsvDelay: uint16(rand.Int31()),
|
||||
}
|
||||
|
||||
localCfg := ChannelConfig{
|
||||
ChannelStateBounds: localStateBounds,
|
||||
CommitmentParams: localRenderingParams,
|
||||
ChannelConstraints: ChannelConstraints{
|
||||
DustLimit: btcutil.Amount(rand.Int63()),
|
||||
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
|
||||
ChanReserve: btcutil.Amount(rand.Int63()),
|
||||
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
|
||||
MaxAcceptedHtlcs: uint16(rand.Int31()),
|
||||
CsvDelay: uint16(rand.Int31()),
|
||||
},
|
||||
MultiSigKey: keychain.KeyDescriptor{
|
||||
PubKey: privKey.PubKey(),
|
||||
},
|
||||
@ -267,22 +257,15 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
||||
PubKey: privKey.PubKey(),
|
||||
},
|
||||
}
|
||||
|
||||
remoteStateBounds := ChannelStateBounds{
|
||||
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
|
||||
ChanReserve: btcutil.Amount(rand.Int63()),
|
||||
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
|
||||
MaxAcceptedHtlcs: uint16(rand.Int31()),
|
||||
}
|
||||
|
||||
remoteRenderingParams := CommitmentParams{
|
||||
DustLimit: btcutil.Amount(rand.Int63()),
|
||||
CsvDelay: uint16(rand.Int31()),
|
||||
}
|
||||
|
||||
remoteCfg := ChannelConfig{
|
||||
ChannelStateBounds: remoteStateBounds,
|
||||
CommitmentParams: remoteRenderingParams,
|
||||
ChannelConstraints: ChannelConstraints{
|
||||
DustLimit: btcutil.Amount(rand.Int63()),
|
||||
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
|
||||
ChanReserve: btcutil.Amount(rand.Int63()),
|
||||
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
|
||||
MaxAcceptedHtlcs: uint16(rand.Int31()),
|
||||
CsvDelay: uint16(rand.Int31()),
|
||||
},
|
||||
MultiSigKey: keychain.KeyDescriptor{
|
||||
PubKey: privKey.PubKey(),
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
@ -327,9 +310,6 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
||||
uniqueOutputIndex.Add(1)
|
||||
op := wire.OutPoint{Hash: key, Index: uniqueOutputIndex.Load()}
|
||||
|
||||
var tapscriptRoot chainhash.Hash
|
||||
copy(tapscriptRoot[:], bytes.Repeat([]byte{1}, 32))
|
||||
|
||||
return &OpenChannel{
|
||||
ChanType: SingleFunderBit | FrozenBit,
|
||||
ChainHash: key,
|
||||
@ -351,7 +331,6 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
||||
FeePerKw: btcutil.Amount(5000),
|
||||
CommitTx: channels.TestFundingTx,
|
||||
CommitSig: bytes.Repeat([]byte{1}, 71),
|
||||
CustomBlob: fn.Some([]byte{1, 2, 3}),
|
||||
},
|
||||
RemoteCommitment: ChannelCommitment{
|
||||
CommitHeight: 0,
|
||||
@ -361,7 +340,6 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
||||
FeePerKw: btcutil.Amount(5000),
|
||||
CommitTx: channels.TestFundingTx,
|
||||
CommitSig: bytes.Repeat([]byte{1}, 71),
|
||||
CustomBlob: fn.Some([]byte{4, 5, 6}),
|
||||
},
|
||||
NumConfsRequired: 4,
|
||||
RemoteCurrentRevocation: privKey.PubKey(),
|
||||
@ -374,9 +352,6 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
||||
ThawHeight: uint32(defaultPendingHeight),
|
||||
InitialLocalBalance: lnwire.MilliSatoshi(9000),
|
||||
InitialRemoteBalance: lnwire.MilliSatoshi(3000),
|
||||
Memo: []byte("test"),
|
||||
TapscriptRoot: fn.Some(tapscriptRoot),
|
||||
CustomBlob: fn.Some([]byte{1, 2, 3}),
|
||||
}
|
||||
}
|
||||
|
||||
@ -391,13 +366,12 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
|
||||
// Create the test channel state, with additional htlcs on the local
|
||||
// and remote commitment.
|
||||
localHtlcs := []HTLC{
|
||||
{
|
||||
Signature: testSig.Serialize(),
|
||||
{Signature: testSig.Serialize(),
|
||||
Incoming: true,
|
||||
Amt: 10,
|
||||
RHash: key,
|
||||
RefundTimeout: 1,
|
||||
OnionBlob: lnmock.MockOnion(),
|
||||
OnionBlob: []byte("onionblob"),
|
||||
},
|
||||
}
|
||||
|
||||
@ -408,7 +382,7 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
|
||||
Amt: 10,
|
||||
RHash: key,
|
||||
RefundTimeout: 1,
|
||||
OnionBlob: lnmock.MockOnion(),
|
||||
OnionBlob: []byte("onionblob"),
|
||||
},
|
||||
}
|
||||
|
||||
@ -584,32 +558,24 @@ func assertCommitmentEqual(t *testing.T, a, b *ChannelCommitment) {
|
||||
func assertRevocationLogEntryEqual(t *testing.T, c *ChannelCommitment,
|
||||
r *RevocationLog) {
|
||||
|
||||
t.Helper()
|
||||
|
||||
// Check the common fields.
|
||||
require.EqualValues(
|
||||
t, r.CommitTxHash.Val, c.CommitTx.TxHash(), "CommitTx mismatch",
|
||||
t, r.CommitTxHash, c.CommitTx.TxHash(), "CommitTx mismatch",
|
||||
)
|
||||
|
||||
// Now check the common fields from the HTLCs.
|
||||
require.Equal(t, len(r.HTLCEntries), len(c.Htlcs), "HTLCs len mismatch")
|
||||
for i, rHtlc := range r.HTLCEntries {
|
||||
cHtlc := c.Htlcs[i]
|
||||
require.Equal(t, rHtlc.RHash.Val[:], cHtlc.RHash[:], "RHash")
|
||||
require.Equal(
|
||||
t, rHtlc.Amt.Val.Int(), cHtlc.Amt.ToSatoshis(), "Amt",
|
||||
)
|
||||
require.Equal(
|
||||
t, rHtlc.RefundTimeout.Val, cHtlc.RefundTimeout,
|
||||
"RefundTimeout",
|
||||
)
|
||||
require.EqualValues(
|
||||
t, rHtlc.OutputIndex.Val, cHtlc.OutputIndex,
|
||||
"OutputIndex",
|
||||
)
|
||||
require.Equal(
|
||||
t, rHtlc.Incoming.Val, cHtlc.Incoming, "Incoming",
|
||||
)
|
||||
require.Equal(t, rHtlc.RHash, cHtlc.RHash, "RHash mismatch")
|
||||
require.Equal(t, rHtlc.Amt, cHtlc.Amt.ToSatoshis(),
|
||||
"Amt mismatch")
|
||||
require.Equal(t, rHtlc.RefundTimeout, cHtlc.RefundTimeout,
|
||||
"RefundTimeout mismatch")
|
||||
require.EqualValues(t, rHtlc.OutputIndex, cHtlc.OutputIndex,
|
||||
"OutputIndex mismatch")
|
||||
require.Equal(t, rHtlc.Incoming, cHtlc.Incoming,
|
||||
"Incoming mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
@ -646,10 +612,8 @@ func TestChannelStateTransition(t *testing.T) {
|
||||
LogIndex: uint64(i * 2),
|
||||
HtlcIndex: uint64(i),
|
||||
}
|
||||
copy(
|
||||
htlc.OnionBlob[:],
|
||||
bytes.Repeat([]byte{2}, lnwire.OnionPacketSize),
|
||||
)
|
||||
htlc.OnionBlob = make([]byte, 10)
|
||||
copy(htlc.OnionBlob[:], bytes.Repeat([]byte{2}, 10))
|
||||
htlcs = append(htlcs, htlc)
|
||||
htlcAmt += htlc.Amt
|
||||
}
|
||||
@ -674,7 +638,6 @@ func TestChannelStateTransition(t *testing.T) {
|
||||
CommitTx: newTx,
|
||||
CommitSig: newSig,
|
||||
Htlcs: htlcs,
|
||||
CustomBlob: fn.Some([]byte{4, 5, 6}),
|
||||
}
|
||||
|
||||
// First update the local node's broadcastable state and also add a
|
||||
@ -684,7 +647,8 @@ func TestChannelStateTransition(t *testing.T) {
|
||||
{
|
||||
LogIndex: 2,
|
||||
UpdateMsg: &lnwire.UpdateAddHTLC{
|
||||
ChanID: lnwire.ChannelID{1, 2, 3},
|
||||
ChanID: lnwire.ChannelID{1, 2, 3},
|
||||
ExtraData: make([]byte, 0),
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -712,14 +676,9 @@ func TestChannelStateTransition(t *testing.T) {
|
||||
// have been updated.
|
||||
updatedChannel, err := cdb.FetchOpenChannels(channel.IdentityPub)
|
||||
require.NoError(t, err, "unable to fetch updated channel")
|
||||
|
||||
assertCommitmentEqual(
|
||||
t, &commitment, &updatedChannel[0].LocalCommitment,
|
||||
)
|
||||
|
||||
assertCommitmentEqual(t, &commitment, &updatedChannel[0].LocalCommitment)
|
||||
numDiskUpdates, err := updatedChannel[0].CommitmentHeight()
|
||||
require.NoError(t, err, "unable to read commitment height from disk")
|
||||
|
||||
if numDiskUpdates != uint64(commitment.CommitHeight) {
|
||||
t.Fatalf("num disk updates doesn't match: %v vs %v",
|
||||
numDiskUpdates, commitment.CommitHeight)
|
||||
@ -747,22 +706,25 @@ func TestChannelStateTransition(t *testing.T) {
|
||||
wireSig,
|
||||
wireSig,
|
||||
},
|
||||
ExtraData: make([]byte, 0),
|
||||
},
|
||||
LogUpdates: []LogUpdate{
|
||||
{
|
||||
LogIndex: 1,
|
||||
UpdateMsg: &lnwire.UpdateAddHTLC{
|
||||
ID: 1,
|
||||
Amount: lnwire.NewMSatFromSatoshis(100),
|
||||
Expiry: 25,
|
||||
ID: 1,
|
||||
Amount: lnwire.NewMSatFromSatoshis(100),
|
||||
Expiry: 25,
|
||||
ExtraData: make([]byte, 0),
|
||||
},
|
||||
},
|
||||
{
|
||||
LogIndex: 2,
|
||||
UpdateMsg: &lnwire.UpdateAddHTLC{
|
||||
ID: 2,
|
||||
Amount: lnwire.NewMSatFromSatoshis(200),
|
||||
Expiry: 50,
|
||||
ID: 2,
|
||||
Amount: lnwire.NewMSatFromSatoshis(200),
|
||||
Expiry: 50,
|
||||
ExtraData: make([]byte, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -822,10 +784,10 @@ func TestChannelStateTransition(t *testing.T) {
|
||||
|
||||
// Check the output indexes are saved as expected.
|
||||
require.EqualValues(
|
||||
t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex.Val,
|
||||
t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex,
|
||||
)
|
||||
require.EqualValues(
|
||||
t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex.Val,
|
||||
t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex,
|
||||
)
|
||||
|
||||
// The two deltas (the original vs the on-disk version) should
|
||||
@ -867,10 +829,10 @@ func TestChannelStateTransition(t *testing.T) {
|
||||
|
||||
// Check the output indexes are saved as expected.
|
||||
require.EqualValues(
|
||||
t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex.Val,
|
||||
t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex,
|
||||
)
|
||||
require.EqualValues(
|
||||
t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex.Val,
|
||||
t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex,
|
||||
)
|
||||
|
||||
assertRevocationLogEntryEqual(t, &oldRemoteCommit, prevCommit)
|
||||
@ -1121,17 +1083,13 @@ func TestFetchWaitingCloseChannels(t *testing.T) {
|
||||
},
|
||||
)
|
||||
|
||||
if err := channel.MarkCommitmentBroadcasted(
|
||||
closeTx, lntypes.Local,
|
||||
); err != nil {
|
||||
if err := channel.MarkCommitmentBroadcasted(closeTx, true); err != nil {
|
||||
t.Fatalf("unable to mark commitment broadcast: %v", err)
|
||||
}
|
||||
|
||||
// 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,
|
||||
); err != nil {
|
||||
if err = channel.MarkCoopBroadcasted(nil, true); err != nil {
|
||||
t.Fatalf("unable to mark nil coop broadcast: %v", err)
|
||||
}
|
||||
_, err := channel.BroadcastedCooperative()
|
||||
@ -1143,9 +1101,7 @@ func TestFetchWaitingCloseChannels(t *testing.T) {
|
||||
// it as coop closed. Later we will test that distinct
|
||||
// transactions are returned for both coop and force closes.
|
||||
closeTx.TxIn[0].PreviousOutPoint.Index ^= 1
|
||||
if err := channel.MarkCoopBroadcasted(
|
||||
closeTx, lntypes.Local,
|
||||
); err != nil {
|
||||
if err := channel.MarkCoopBroadcasted(closeTx, true); err != nil {
|
||||
t.Fatalf("unable to mark coop broadcast: %v", err)
|
||||
}
|
||||
}
|
||||
@ -1198,70 +1154,6 @@ func TestFetchWaitingCloseChannels(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestShutdownInfo tests that a channel's shutdown info can correctly be
|
||||
// persisted and retrieved.
|
||||
func TestShutdownInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
localInit bool
|
||||
}{
|
||||
{
|
||||
name: "local node initiated",
|
||||
localInit: true,
|
||||
},
|
||||
{
|
||||
name: "remote node initiated",
|
||||
localInit: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testShutdownInfo(t, test.localInit)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testShutdownInfo(t *testing.T, locallyInitiated bool) {
|
||||
fullDB, err := MakeTestDB(t)
|
||||
require.NoError(t, err, "unable to make test database")
|
||||
|
||||
cdb := fullDB.ChannelStateDB()
|
||||
|
||||
// First a test channel.
|
||||
channel := createTestChannel(t, cdb)
|
||||
|
||||
// We haven't persisted any shutdown info for this channel yet.
|
||||
_, err = channel.ShutdownInfo()
|
||||
require.Error(t, err, ErrNoShutdownInfo)
|
||||
|
||||
// Construct a new delivery script and create a new ShutdownInfo object.
|
||||
script := []byte{1, 3, 4, 5}
|
||||
|
||||
// Create a ShutdownInfo struct.
|
||||
shutdownInfo := NewShutdownInfo(script, locallyInitiated)
|
||||
|
||||
// Persist the shutdown info.
|
||||
require.NoError(t, channel.MarkShutdownSent(shutdownInfo))
|
||||
|
||||
// We should now be able to retrieve the shutdown info.
|
||||
info, err := channel.ShutdownInfo()
|
||||
require.NoError(t, err)
|
||||
require.True(t, info.IsSome())
|
||||
|
||||
// Assert that the decoded values of the shutdown info are correct.
|
||||
info.WhenSome(func(info ShutdownInfo) {
|
||||
require.EqualValues(t, script, info.DeliveryScript.Val)
|
||||
require.Equal(t, locallyInitiated, info.LocalInitiator.Val)
|
||||
})
|
||||
}
|
||||
|
||||
// TestRefresh asserts that Refresh updates the in-memory state of another
|
||||
// OpenChannel to reflect a preceding call to MarkOpen on a different
|
||||
// OpenChannel.
|
||||
@ -1367,7 +1259,7 @@ func TestCloseInitiator(t *testing.T) {
|
||||
// by the local party.
|
||||
updateChannel: func(c *OpenChannel) error {
|
||||
return c.MarkCoopBroadcasted(
|
||||
&wire.MsgTx{}, lntypes.Local,
|
||||
&wire.MsgTx{}, true,
|
||||
)
|
||||
},
|
||||
expectedStatuses: []ChannelStatus{
|
||||
@ -1381,7 +1273,7 @@ func TestCloseInitiator(t *testing.T) {
|
||||
// by the remote party.
|
||||
updateChannel: func(c *OpenChannel) error {
|
||||
return c.MarkCoopBroadcasted(
|
||||
&wire.MsgTx{}, lntypes.Remote,
|
||||
&wire.MsgTx{}, false,
|
||||
)
|
||||
},
|
||||
expectedStatuses: []ChannelStatus{
|
||||
@ -1395,7 +1287,7 @@ func TestCloseInitiator(t *testing.T) {
|
||||
// local initiator.
|
||||
updateChannel: func(c *OpenChannel) error {
|
||||
return c.MarkCommitmentBroadcasted(
|
||||
&wire.MsgTx{}, lntypes.Local,
|
||||
&wire.MsgTx{}, true,
|
||||
)
|
||||
},
|
||||
expectedStatuses: []ChannelStatus{
|
||||
@ -1635,130 +1527,3 @@ func TestFinalHtlcs(t *testing.T) {
|
||||
_, err = cdb.LookupFinalHtlc(chanID, unknownHtlcID)
|
||||
require.ErrorIs(t, err, ErrHtlcUnknown)
|
||||
}
|
||||
|
||||
// TestHTLCsExtraData tests serialization and deserialization of HTLCs
|
||||
// combined with extra data.
|
||||
func TestHTLCsExtraData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mockHtlc := HTLC{
|
||||
Signature: testSig.Serialize(),
|
||||
Incoming: false,
|
||||
Amt: 10,
|
||||
RHash: key,
|
||||
RefundTimeout: 1,
|
||||
OnionBlob: lnmock.MockOnion(),
|
||||
}
|
||||
|
||||
// Add a blinding point to a htlc.
|
||||
blindingPointHTLC := HTLC{
|
||||
Signature: testSig.Serialize(),
|
||||
Incoming: false,
|
||||
Amt: 10,
|
||||
RHash: key,
|
||||
RefundTimeout: 1,
|
||||
OnionBlob: lnmock.MockOnion(),
|
||||
BlindingPoint: tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](
|
||||
pubKey,
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
// Custom channel data htlc with a blinding point.
|
||||
customDataHTLC := HTLC{
|
||||
Signature: testSig.Serialize(),
|
||||
Incoming: false,
|
||||
Amt: 10,
|
||||
RHash: key,
|
||||
RefundTimeout: 1,
|
||||
OnionBlob: lnmock.MockOnion(),
|
||||
BlindingPoint: tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](
|
||||
pubKey,
|
||||
),
|
||||
),
|
||||
CustomRecords: map[uint64][]byte{
|
||||
uint64(lnwire.MinCustomRecordsTlvType + 3): {1, 2, 3},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
htlcs []HTLC
|
||||
blindingIdx int
|
||||
}{
|
||||
{
|
||||
// Serialize multiple HLTCs with no extra data to
|
||||
// assert that there is no regression for HTLCs with
|
||||
// no extra data.
|
||||
name: "no extra data",
|
||||
htlcs: []HTLC{
|
||||
mockHtlc, mockHtlc,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Some HTLCs with extra data, some without.
|
||||
name: "mixed extra data",
|
||||
htlcs: []HTLC{
|
||||
mockHtlc,
|
||||
blindingPointHTLC,
|
||||
mockHtlc,
|
||||
customDataHTLC,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var b bytes.Buffer
|
||||
err := SerializeHtlcs(&b, testCase.htlcs...)
|
||||
require.NoError(t, err)
|
||||
|
||||
r := bytes.NewReader(b.Bytes())
|
||||
htlcs, err := DeserializeHtlcs(r)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.EqualValues(t, len(testCase.htlcs), len(htlcs))
|
||||
for i, htlc := range htlcs {
|
||||
// We use the extra data field when we
|
||||
// serialize, so we set to nil to be able to
|
||||
// assert on equal for the test.
|
||||
htlc.ExtraData = nil
|
||||
require.Equal(t, testCase.htlcs[i], htlc)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestOnionBlobIncorrectLength tests HTLC deserialization in the case where
|
||||
// the OnionBlob saved on disk is of an unexpected length. This error case is
|
||||
// only expected in the case of database corruption (or some severe protocol
|
||||
// breakdown/bug). A HTLC is manually serialized because we cannot force a
|
||||
// case where we write an onion blob of incorrect length.
|
||||
func TestOnionBlobIncorrectLength(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var b bytes.Buffer
|
||||
|
||||
var numHtlcs uint16 = 1
|
||||
require.NoError(t, WriteElement(&b, numHtlcs))
|
||||
|
||||
require.NoError(t, WriteElements(
|
||||
&b,
|
||||
// Number of HTLCs.
|
||||
numHtlcs,
|
||||
// Signature, incoming, amount, Rhash, Timeout.
|
||||
testSig.Serialize(), false, lnwire.MilliSatoshi(10), key,
|
||||
uint32(1),
|
||||
// Write an onion blob that is half of our expected size.
|
||||
bytes.Repeat([]byte{1}, lnwire.OnionPacketSize/2),
|
||||
))
|
||||
|
||||
_, err := DeserializeHtlcs(&b)
|
||||
require.ErrorIs(t, err, ErrOnionBlobLength)
|
||||
}
|
||||
|
||||
@ -78,8 +78,7 @@ func WriteElement(w io.Writer, element interface{}) error {
|
||||
|
||||
if e.PubKey != nil {
|
||||
if err := binary.Write(w, byteOrder, true); err != nil {
|
||||
return fmt.Errorf("error writing serialized "+
|
||||
"element: %w", err)
|
||||
return fmt.Errorf("error writing serialized element: %s", err)
|
||||
}
|
||||
|
||||
return WriteElement(w, e.PubKey)
|
||||
|
||||
206
channeldb/db.go
206
channeldb/db.go
@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/go-errors/errors"
|
||||
mig "github.com/lightningnetwork/lnd/channeldb/migration"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration12"
|
||||
@ -25,10 +24,8 @@ import (
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration27"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration29"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration30"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration31"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/invoices"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
@ -278,14 +275,6 @@ var (
|
||||
number: 29,
|
||||
migration: migration29.MigrateChanID,
|
||||
},
|
||||
{
|
||||
// Removes the "sweeper-last-tx" bucket. Although we
|
||||
// do not have a mandatory version 30 we skip this
|
||||
// version because its naming is already used for the
|
||||
// first optional migration.
|
||||
number: 31,
|
||||
migration: migration31.DeleteLastPublishedTxTLB,
|
||||
},
|
||||
}
|
||||
|
||||
// optionalVersions stores all optional migrations that are applied
|
||||
@ -313,6 +302,11 @@ var (
|
||||
// channelOpeningState for each channel that is currently in the process
|
||||
// of being opened.
|
||||
channelOpeningStateBucket = []byte("channelOpeningState")
|
||||
|
||||
// initialChannelFwdingPolicyBucket is the database bucket used to store
|
||||
// the forwarding policy for each permanent channel that is currently
|
||||
// in the process of being opened.
|
||||
initialChannelFwdingPolicyBucket = []byte("initialChannelFwdingPolicy")
|
||||
)
|
||||
|
||||
// DB is the primary datastore for the lnd daemon. The database stores
|
||||
@ -497,7 +491,7 @@ func initChannelDB(db kvdb.Backend) error {
|
||||
return putMeta(meta, tx)
|
||||
}, func() {})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create new channeldb: %w", err)
|
||||
return fmt.Errorf("unable to create new channeldb: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -643,7 +637,7 @@ func (c *ChannelStateDB) fetchNodeChannels(chainBucket kvdb.RBucket) (
|
||||
oChannel, err := fetchOpenChannel(chanBucket, &outPoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read channel data for "+
|
||||
"chan_point=%v: %w", outPoint, err)
|
||||
"chan_point=%v: %v", outPoint, err)
|
||||
}
|
||||
oChannel.Db = c
|
||||
|
||||
@ -660,94 +654,20 @@ func (c *ChannelStateDB) fetchNodeChannels(chainBucket kvdb.RBucket) (
|
||||
|
||||
// FetchChannel attempts to locate a channel specified by the passed channel
|
||||
// point. If the channel cannot be found, then an error will be returned.
|
||||
// Optionally an existing db tx can be supplied.
|
||||
// Optionally an existing db tx can be supplied. Optionally an existing db tx
|
||||
// can be supplied.
|
||||
func (c *ChannelStateDB) FetchChannel(tx kvdb.RTx, chanPoint wire.OutPoint) (
|
||||
*OpenChannel, error) {
|
||||
|
||||
var targetChanPoint bytes.Buffer
|
||||
var (
|
||||
targetChan *OpenChannel
|
||||
targetChanPoint bytes.Buffer
|
||||
)
|
||||
|
||||
if err := writeOutpoint(&targetChanPoint, &chanPoint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targetChanPointBytes := targetChanPoint.Bytes()
|
||||
selector := func(chainBkt walletdb.ReadBucket) ([]byte, *wire.OutPoint,
|
||||
error) {
|
||||
|
||||
return targetChanPointBytes, &chanPoint, nil
|
||||
}
|
||||
|
||||
return c.channelScanner(tx, selector)
|
||||
}
|
||||
|
||||
// FetchChannelByID attempts to locate a channel specified by the passed channel
|
||||
// ID. If the channel cannot be found, then an error will be returned.
|
||||
// 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) {
|
||||
|
||||
var (
|
||||
targetChanPointBytes []byte
|
||||
targetChanPoint *wire.OutPoint
|
||||
|
||||
// errChanFound is used to signal that the channel has
|
||||
// been found so that iteration through the DB buckets
|
||||
// can stop.
|
||||
errChanFound = errors.New("channel found")
|
||||
)
|
||||
err := chainBkt.ForEach(func(k, _ []byte) error {
|
||||
var outPoint wire.OutPoint
|
||||
err := readOutpoint(bytes.NewReader(k), &outPoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chanID := lnwire.NewChanIDFromOutPoint(outPoint)
|
||||
if chanID != id {
|
||||
return nil
|
||||
}
|
||||
|
||||
targetChanPoint = &outPoint
|
||||
targetChanPointBytes = k
|
||||
|
||||
return errChanFound
|
||||
})
|
||||
if err != nil && !errors.Is(err, errChanFound) {
|
||||
return nil, nil, err
|
||||
}
|
||||
if targetChanPoint == nil {
|
||||
return nil, nil, ErrChannelNotFound
|
||||
}
|
||||
|
||||
return targetChanPointBytes, targetChanPoint, nil
|
||||
}
|
||||
|
||||
return c.channelScanner(tx, selector)
|
||||
}
|
||||
|
||||
// channelSelector describes a function that takes a chain-hash bucket from
|
||||
// within the open-channel DB and returns the wanted channel point bytes, and
|
||||
// channel point. It must return the ErrChannelNotFound error if the wanted
|
||||
// channel is not in the given bucket.
|
||||
type channelSelector func(chainBkt walletdb.ReadBucket) ([]byte, *wire.OutPoint,
|
||||
error)
|
||||
|
||||
// channelScanner will traverse the DB to each chain-hash bucket of each node
|
||||
// pub-key bucket in the open-channel-bucket. The chanSelector will then be used
|
||||
// to fetch the wanted channel outpoint from the chain bucket.
|
||||
func (c *ChannelStateDB) channelScanner(tx kvdb.RTx,
|
||||
chanSelect channelSelector) (*OpenChannel, error) {
|
||||
|
||||
var (
|
||||
targetChan *OpenChannel
|
||||
|
||||
// errChanFound is used to signal that the channel has been
|
||||
// found so that iteration through the DB buckets can stop.
|
||||
errChanFound = errors.New("channel found")
|
||||
)
|
||||
|
||||
// chanScan will traverse the following bucket structure:
|
||||
// * nodePub => chainHash => chanPoint
|
||||
//
|
||||
@ -765,8 +685,8 @@ func (c *ChannelStateDB) channelScanner(tx kvdb.RTx,
|
||||
}
|
||||
|
||||
// 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.
|
||||
// we have channels with, we don't know the entire set, so
|
||||
// we'll check them all.
|
||||
return openChanBucket.ForEach(func(nodePub, v []byte) error {
|
||||
// Ensure that this is a key the same size as a pubkey,
|
||||
// and also that it leads directly to a bucket.
|
||||
@ -774,9 +694,7 @@ func (c *ChannelStateDB) channelScanner(tx kvdb.RTx,
|
||||
return nil
|
||||
}
|
||||
|
||||
nodeChanBucket := openChanBucket.NestedReadBucket(
|
||||
nodePub,
|
||||
)
|
||||
nodeChanBucket := openChanBucket.NestedReadBucket(nodePub)
|
||||
if nodeChanBucket == nil {
|
||||
return nil
|
||||
}
|
||||
@ -797,30 +715,20 @@ func (c *ChannelStateDB) channelScanner(tx kvdb.RTx,
|
||||
)
|
||||
if chainBucket == nil {
|
||||
return fmt.Errorf("unable to read "+
|
||||
"bucket for chain=%x",
|
||||
chainHash)
|
||||
"bucket for chain=%x", chainHash[:])
|
||||
}
|
||||
|
||||
// Finally, we reach the leaf bucket that stores
|
||||
// Finally we reach the leaf bucket that stores
|
||||
// all the chanPoints for this node.
|
||||
targetChanBytes, chanPoint, err := chanSelect(
|
||||
chainBucket,
|
||||
)
|
||||
if errors.Is(err, ErrChannelNotFound) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chanBucket := chainBucket.NestedReadBucket(
|
||||
targetChanBytes,
|
||||
targetChanPoint.Bytes(),
|
||||
)
|
||||
if chanBucket == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
channel, err := fetchOpenChannel(
|
||||
chanBucket, chanPoint,
|
||||
chanBucket, &chanPoint,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -829,7 +737,7 @@ func (c *ChannelStateDB) channelScanner(tx kvdb.RTx,
|
||||
targetChan = channel
|
||||
targetChan.Db = c
|
||||
|
||||
return errChanFound
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -840,7 +748,7 @@ func (c *ChannelStateDB) channelScanner(tx kvdb.RTx,
|
||||
} else {
|
||||
err = chanScan(tx)
|
||||
}
|
||||
if err != nil && !errors.Is(err, errChanFound) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -849,7 +757,7 @@ func (c *ChannelStateDB) channelScanner(tx kvdb.RTx,
|
||||
}
|
||||
|
||||
// If we can't find the channel, then we return with an error, as we
|
||||
// have nothing to back up.
|
||||
// have nothing to backup.
|
||||
return nil, ErrChannelNotFound
|
||||
}
|
||||
|
||||
@ -1429,6 +1337,64 @@ func (c *ChannelStateDB) AbandonChannel(chanPoint *wire.OutPoint,
|
||||
return dbChan.CloseChannel(summary, ChanStatusLocalCloseInitiator)
|
||||
}
|
||||
|
||||
// SaveInitialFwdingPolicy saves the serialized forwarding policy for the
|
||||
// provided permanent channel id to the initialChannelFwdingPolicyBucket.
|
||||
func (c *ChannelStateDB) SaveInitialFwdingPolicy(chanID,
|
||||
forwardingPolicy []byte) error {
|
||||
|
||||
return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
|
||||
bucket, err := tx.CreateTopLevelBucket(
|
||||
initialChannelFwdingPolicyBucket,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(chanID, forwardingPolicy)
|
||||
}, func() {})
|
||||
}
|
||||
|
||||
// GetInitialFwdingPolicy 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.
|
||||
func (c *ChannelStateDB) GetInitialFwdingPolicy(chanID []byte) ([]byte, error) {
|
||||
var serializedState []byte
|
||||
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
|
||||
bucket := tx.ReadBucket(initialChannelFwdingPolicyBucket)
|
||||
if bucket == nil {
|
||||
// If the bucket does not exist, it means we
|
||||
// never added a channel fees to the db, so
|
||||
// return ErrChannelNotFound.
|
||||
return ErrChannelNotFound
|
||||
}
|
||||
|
||||
stateBytes := bucket.Get(chanID)
|
||||
if stateBytes == nil {
|
||||
return ErrChannelNotFound
|
||||
}
|
||||
|
||||
serializedState = append(serializedState, stateBytes...)
|
||||
|
||||
return nil
|
||||
}, func() {
|
||||
serializedState = nil
|
||||
})
|
||||
return serializedState, err
|
||||
}
|
||||
|
||||
// DeleteInitialFwdingPolicy removes the forwarding policy for a given channel
|
||||
// from the database.
|
||||
func (c *ChannelStateDB) DeleteInitialFwdingPolicy(chanID []byte) error {
|
||||
return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
|
||||
bucket := tx.ReadWriteBucket(initialChannelFwdingPolicyBucket)
|
||||
if bucket == nil {
|
||||
return ErrChannelNotFound
|
||||
}
|
||||
|
||||
return bucket.Delete(chanID)
|
||||
}, func() {})
|
||||
}
|
||||
|
||||
// SaveChannelOpeningState saves the serialized channel state for the provided
|
||||
// chanPoint to the channelOpeningStateBucket.
|
||||
func (c *ChannelStateDB) SaveChannelOpeningState(outPoint,
|
||||
@ -1825,14 +1791,6 @@ func (c *ChannelStateDB) PutOnchainFinalHtlcOutcome(
|
||||
}, func() {})
|
||||
}
|
||||
|
||||
// MakeTestInvoiceDB is used to create a test invoice database for testing
|
||||
// purposes. It simply calls into MakeTestDB so the same modifiers can be used.
|
||||
func MakeTestInvoiceDB(t *testing.T, modifiers ...OptionModifier) (
|
||||
invoices.InvoiceDB, error) {
|
||||
|
||||
return MakeTestDB(t, modifiers...)
|
||||
}
|
||||
|
||||
// MakeTestDB creates a new instance of the ChannelDB for testing purposes.
|
||||
// A callback which cleans up the created temporary directories is also
|
||||
// returned and intended to be executed after the test completes.
|
||||
|
||||
@ -12,9 +12,9 @@ import (
|
||||
"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/keychain"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/shachain"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -141,7 +141,7 @@ func TestFetchClosedChannelForID(t *testing.T) {
|
||||
state.FundingOutpoint.Index = i
|
||||
|
||||
// We calculate the ChannelID and use it to fetch the summary.
|
||||
cid := lnwire.NewChanIDFromOutPoint(state.FundingOutpoint)
|
||||
cid := lnwire.NewChanIDFromOutPoint(&state.FundingOutpoint)
|
||||
fetchedSummary, err := cdb.FetchClosedChannelForID(cid)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to fetch close summary: %v", err)
|
||||
@ -159,7 +159,7 @@ func TestFetchClosedChannelForID(t *testing.T) {
|
||||
// As a final test we make sure that we get ErrClosedChannelNotFound
|
||||
// for a ChannelID we didn't add to the DB.
|
||||
state.FundingOutpoint.Index++
|
||||
cid := lnwire.NewChanIDFromOutPoint(state.FundingOutpoint)
|
||||
cid := lnwire.NewChanIDFromOutPoint(&state.FundingOutpoint)
|
||||
_, err = cdb.FetchClosedChannelForID(cid)
|
||||
if err != ErrClosedChannelNotFound {
|
||||
t.Fatalf("expected ErrClosedChannelNotFound, instead got: %v", err)
|
||||
@ -238,16 +238,10 @@ func TestFetchChannel(t *testing.T) {
|
||||
|
||||
// The decoded channel state should be identical to what we stored
|
||||
// above.
|
||||
require.Equal(t, channelState, dbChannel)
|
||||
|
||||
// Next, attempt to fetch the channel by its channel ID.
|
||||
chanID := lnwire.NewChanIDFromOutPoint(channelState.FundingOutpoint)
|
||||
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
|
||||
// above.
|
||||
require.Equal(t, channelState, dbChannel)
|
||||
if !reflect.DeepEqual(channelState, dbChannel) {
|
||||
t.Fatalf("channel state doesn't match:: %v vs %v",
|
||||
spew.Sdump(channelState), spew.Sdump(dbChannel))
|
||||
}
|
||||
|
||||
// If we attempt to query for a non-existent channel, then we should
|
||||
// get an error.
|
||||
@ -258,11 +252,9 @@ func TestFetchChannel(t *testing.T) {
|
||||
channelState2.FundingOutpoint.Index = uniqueOutputIndex.Load()
|
||||
|
||||
_, err = cdb.FetchChannel(nil, channelState2.FundingOutpoint)
|
||||
require.ErrorIs(t, err, ErrChannelNotFound)
|
||||
|
||||
chanID2 := lnwire.NewChanIDFromOutPoint(channelState2.FundingOutpoint)
|
||||
_, err = cdb.FetchChannelByID(nil, chanID2)
|
||||
require.ErrorIs(t, err, ErrChannelNotFound)
|
||||
if err == nil {
|
||||
t.Fatalf("expected query to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func genRandomChannelShell() (*ChannelShell, error) {
|
||||
@ -292,10 +284,6 @@ func genRandomChannelShell() (*ChannelShell, error) {
|
||||
}
|
||||
shaChainProducer := shachain.NewRevocationProducer(*revRoot)
|
||||
|
||||
commitParams := CommitmentParams{
|
||||
CsvDelay: uint16(rand.Int63()),
|
||||
}
|
||||
|
||||
return &ChannelShell{
|
||||
NodeAddrs: []net.Addr{&net.TCPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
@ -310,7 +298,9 @@ func genRandomChannelShell() (*ChannelShell, error) {
|
||||
),
|
||||
IdentityPub: pub,
|
||||
LocalChanCfg: ChannelConfig{
|
||||
CommitmentParams: commitParams,
|
||||
ChannelConstraints: ChannelConstraints{
|
||||
CsvDelay: uint16(rand.Int63()),
|
||||
},
|
||||
PaymentBasePoint: keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(rand.Int63()),
|
||||
@ -609,9 +599,7 @@ func TestFetchChannels(t *testing.T) {
|
||||
channelIDOption(pendingWaitingChan),
|
||||
)
|
||||
|
||||
err = pendingClosing.MarkCoopBroadcasted(
|
||||
nil, lntypes.Local,
|
||||
)
|
||||
err = pendingClosing.MarkCoopBroadcasted(nil, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@ -631,9 +619,7 @@ func TestFetchChannels(t *testing.T) {
|
||||
channelIDOption(openWaitingChan),
|
||||
openChannelOption(),
|
||||
)
|
||||
err = openClosing.MarkCoopBroadcasted(
|
||||
nil, lntypes.Local,
|
||||
)
|
||||
err = openClosing.MarkCoopBroadcasted(nil, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
@ -59,22 +59,22 @@ type duplicateHTLCAttemptInfo struct {
|
||||
route 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) {
|
||||
// fetchDuplicatePaymentStatus fetches the payment status of the payment. If the
|
||||
// payment isn't found, it will default to "StatusUnknown".
|
||||
func fetchDuplicatePaymentStatus(bucket kvdb.RBucket) PaymentStatus {
|
||||
if bucket.Get(duplicatePaymentSettleInfoKey) != nil {
|
||||
return StatusSucceeded, nil
|
||||
return StatusSucceeded
|
||||
}
|
||||
|
||||
if bucket.Get(duplicatePaymentFailInfoKey) != nil {
|
||||
return StatusFailed, nil
|
||||
return StatusFailed
|
||||
}
|
||||
|
||||
if bucket.Get(duplicatePaymentCreationInfoKey) != nil {
|
||||
return StatusInFlight, nil
|
||||
return StatusInFlight
|
||||
}
|
||||
|
||||
return 0, ErrPaymentNotInitiated
|
||||
return StatusUnknown
|
||||
}
|
||||
|
||||
func deserializeDuplicateHTLCAttemptInfo(r io.Reader) (
|
||||
@ -138,10 +138,7 @@ func fetchDuplicatePayment(bucket kvdb.RBucket) (*MPPayment, error) {
|
||||
sequenceNum := binary.BigEndian.Uint64(seqBytes)
|
||||
|
||||
// Get the payment status.
|
||||
paymentStatus, err := fetchDuplicatePaymentStatus(bucket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paymentStatus := fetchDuplicatePaymentStatus(bucket)
|
||||
|
||||
// Get the PaymentCreationInfo.
|
||||
b := bucket.Get(duplicatePaymentCreationInfoKey)
|
||||
|
||||
@ -43,10 +43,6 @@ var (
|
||||
// created.
|
||||
ErrMetaNotFound = fmt.Errorf("unable to locate meta information")
|
||||
|
||||
// ErrClosedScidsNotFound is returned when the closed scid bucket
|
||||
// hasn't been created.
|
||||
ErrClosedScidsNotFound = fmt.Errorf("closed scid bucket doesn't exist")
|
||||
|
||||
// ErrGraphNotFound is returned when at least one of the components of
|
||||
// graph doesn't exist.
|
||||
ErrGraphNotFound = fmt.Errorf("graph bucket not initialized")
|
||||
@ -79,10 +75,6 @@ var (
|
||||
// but it is marked as a zombie within the zombie index.
|
||||
ErrZombieEdge = errors.New("edge marked as zombie")
|
||||
|
||||
// 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")
|
||||
|
||||
// ErrEdgeAlreadyExist is returned when edge with specific
|
||||
// channel id can't be added because it already exist.
|
||||
ErrEdgeAlreadyExist = fmt.Errorf("edge already exist")
|
||||
|
||||
@ -211,11 +211,6 @@ func (f *PkgFilter) Decode(r io.Reader) error {
|
||||
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
|
||||
@ -286,27 +281,6 @@ func NewFwdPkg(source lnwire.ShortChannelID, height uint64,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
@ -141,32 +141,9 @@ func checkPkgFilterEncodeDecode(t *testing.T, i uint16, f *channeldb.PkgFilter)
|
||||
}
|
||||
|
||||
var (
|
||||
chanID = lnwire.NewChanIDFromOutPoint(wire.OutPoint{})
|
||||
)
|
||||
chanID = lnwire.NewChanIDFromOutPoint(&wire.OutPoint{})
|
||||
|
||||
func testSettleFails() []channeldb.LogUpdate {
|
||||
return []channeldb.LogUpdate{
|
||||
{
|
||||
LogIndex: 2,
|
||||
UpdateMsg: &lnwire.UpdateFulfillHTLC{
|
||||
ChanID: chanID,
|
||||
ID: 0,
|
||||
PaymentPreimage: [32]byte{0},
|
||||
},
|
||||
},
|
||||
{
|
||||
LogIndex: 3,
|
||||
UpdateMsg: &lnwire.UpdateFailHTLC{
|
||||
ChanID: chanID,
|
||||
ID: 1,
|
||||
Reason: []byte{},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testAdds() []channeldb.LogUpdate {
|
||||
return []channeldb.LogUpdate{
|
||||
adds = []channeldb.LogUpdate{
|
||||
{
|
||||
LogIndex: 0,
|
||||
UpdateMsg: &lnwire.UpdateAddHTLC{
|
||||
@ -188,7 +165,26 @@ func testAdds() []channeldb.LogUpdate {
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
settleFails = []channeldb.LogUpdate{
|
||||
{
|
||||
LogIndex: 2,
|
||||
UpdateMsg: &lnwire.UpdateFulfillHTLC{
|
||||
ChanID: chanID,
|
||||
ID: 0,
|
||||
PaymentPreimage: [32]byte{0},
|
||||
},
|
||||
},
|
||||
{
|
||||
LogIndex: 3,
|
||||
UpdateMsg: &lnwire.UpdateFailHTLC{
|
||||
ChanID: chanID,
|
||||
ID: 1,
|
||||
Reason: []byte{},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// TestPackagerEmptyFwdPkg checks that the state transitions exhibited by a
|
||||
// forwarding package that contains no adds, fails or settles. We expect that
|
||||
@ -277,8 +273,6 @@ func TestPackagerOnlyAdds(t *testing.T) {
|
||||
t.Fatalf("no forwarding packages should exist, found %d", len(fwdPkgs))
|
||||
}
|
||||
|
||||
adds := testAdds()
|
||||
|
||||
// Next, create and write a new forwarding package that only has add
|
||||
// htlcs.
|
||||
fwdPkg := channeldb.NewFwdPkg(shortChanID, 0, adds, nil)
|
||||
@ -383,7 +377,6 @@ func TestPackagerOnlySettleFails(t *testing.T) {
|
||||
|
||||
// Next, create and write a new forwarding package that only has add
|
||||
// htlcs.
|
||||
settleFails := testSettleFails()
|
||||
fwdPkg := channeldb.NewFwdPkg(shortChanID, 0, nil, settleFails)
|
||||
|
||||
nSettleFails := len(settleFails)
|
||||
@ -486,11 +479,8 @@ func TestPackagerAddsThenSettleFails(t *testing.T) {
|
||||
t.Fatalf("no forwarding packages should exist, found %d", len(fwdPkgs))
|
||||
}
|
||||
|
||||
adds := testAdds()
|
||||
|
||||
// Next, create and write a new forwarding package that only has add
|
||||
// htlcs.
|
||||
settleFails := testSettleFails()
|
||||
fwdPkg := channeldb.NewFwdPkg(shortChanID, 0, adds, settleFails)
|
||||
|
||||
nAdds := len(adds)
|
||||
@ -622,11 +612,8 @@ func TestPackagerSettleFailsThenAdds(t *testing.T) {
|
||||
t.Fatalf("no forwarding packages should exist, found %d", len(fwdPkgs))
|
||||
}
|
||||
|
||||
adds := testAdds()
|
||||
|
||||
// Next, create and write a new forwarding package that has both add
|
||||
// and settle/fail htlcs.
|
||||
settleFails := testSettleFails()
|
||||
fwdPkg := channeldb.NewFwdPkg(shortChanID, 0, adds, settleFails)
|
||||
|
||||
nAdds := len(adds)
|
||||
|
||||
@ -1,111 +0,0 @@
|
||||
package channeldb
|
||||
|
||||
import (
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
var (
|
||||
// initialChannelForwardingPolicyBucket is the database bucket used to
|
||||
// store the forwarding policy for each permanent channel that is
|
||||
// currently in the process of being opened.
|
||||
initialChannelForwardingPolicyBucket = []byte(
|
||||
"initialChannelFwdingPolicy",
|
||||
)
|
||||
)
|
||||
|
||||
// SaveInitialForwardingPolicy saves the serialized forwarding policy for the
|
||||
// provided permanent channel id to the initialChannelForwardingPolicyBucket.
|
||||
func (c *ChannelStateDB) SaveInitialForwardingPolicy(chanID lnwire.ChannelID,
|
||||
forwardingPolicy *models.ForwardingPolicy) error {
|
||||
|
||||
chanIDCopy := make([]byte, 32)
|
||||
copy(chanIDCopy, chanID[:])
|
||||
|
||||
scratch := make([]byte, 36)
|
||||
byteOrder.PutUint64(scratch[:8], uint64(forwardingPolicy.MinHTLCOut))
|
||||
byteOrder.PutUint64(scratch[8:16], uint64(forwardingPolicy.MaxHTLC))
|
||||
byteOrder.PutUint64(scratch[16:24], uint64(forwardingPolicy.BaseFee))
|
||||
byteOrder.PutUint64(scratch[24:32], uint64(forwardingPolicy.FeeRate))
|
||||
byteOrder.PutUint32(scratch[32:], forwardingPolicy.TimeLockDelta)
|
||||
|
||||
return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
|
||||
bucket, err := tx.CreateTopLevelBucket(
|
||||
initialChannelForwardingPolicyBucket,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(chanIDCopy, scratch)
|
||||
}, func() {})
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (c *ChannelStateDB) GetInitialForwardingPolicy(
|
||||
chanID lnwire.ChannelID) (*models.ForwardingPolicy, error) {
|
||||
|
||||
chanIDCopy := make([]byte, 32)
|
||||
copy(chanIDCopy, chanID[:])
|
||||
|
||||
var forwardingPolicy *models.ForwardingPolicy
|
||||
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
|
||||
bucket := tx.ReadBucket(initialChannelForwardingPolicyBucket)
|
||||
if bucket == nil {
|
||||
// If the bucket does not exist, it means we
|
||||
// never added a channel fees to the db, so
|
||||
// return ErrChannelNotFound.
|
||||
return ErrChannelNotFound
|
||||
}
|
||||
|
||||
stateBytes := bucket.Get(chanIDCopy)
|
||||
if stateBytes == nil {
|
||||
return ErrChannelNotFound
|
||||
}
|
||||
|
||||
forwardingPolicy = &models.ForwardingPolicy{
|
||||
MinHTLCOut: lnwire.MilliSatoshi(
|
||||
byteOrder.Uint64(stateBytes[:8]),
|
||||
),
|
||||
MaxHTLC: lnwire.MilliSatoshi(
|
||||
byteOrder.Uint64(stateBytes[8:16]),
|
||||
),
|
||||
BaseFee: lnwire.MilliSatoshi(
|
||||
byteOrder.Uint64(stateBytes[16:24]),
|
||||
),
|
||||
FeeRate: lnwire.MilliSatoshi(
|
||||
byteOrder.Uint64(stateBytes[24:32]),
|
||||
),
|
||||
TimeLockDelta: byteOrder.Uint32(stateBytes[32:36]),
|
||||
}
|
||||
|
||||
return nil
|
||||
}, func() {
|
||||
forwardingPolicy = nil
|
||||
})
|
||||
|
||||
return forwardingPolicy, err
|
||||
}
|
||||
|
||||
// DeleteInitialForwardingPolicy removes the forwarding policy for a given
|
||||
// channel from the database.
|
||||
func (c *ChannelStateDB) DeleteInitialForwardingPolicy(
|
||||
chanID lnwire.ChannelID) error {
|
||||
|
||||
chanIDCopy := make([]byte, 32)
|
||||
copy(chanIDCopy, chanID[:])
|
||||
|
||||
return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
|
||||
bucket := tx.ReadWriteBucket(
|
||||
initialChannelForwardingPolicyBucket,
|
||||
)
|
||||
if bucket == nil {
|
||||
return ErrChannelNotFound
|
||||
}
|
||||
|
||||
return bucket.Delete(chanIDCopy)
|
||||
}, func() {})
|
||||
}
|
||||
1293
channeldb/graph.go
1293
channeldb/graph.go
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,6 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
@ -28,9 +27,94 @@ type GraphCacheNode interface {
|
||||
// error, then the iteration is halted with the error propagated back up
|
||||
// to the caller.
|
||||
ForEachChannel(kvdb.RTx,
|
||||
func(kvdb.RTx, *models.ChannelEdgeInfo,
|
||||
*models.ChannelEdgePolicy,
|
||||
*models.ChannelEdgePolicy) error) error
|
||||
func(kvdb.RTx, *ChannelEdgeInfo, *ChannelEdgePolicy,
|
||||
*ChannelEdgePolicy) error) error
|
||||
}
|
||||
|
||||
// CachedEdgePolicy is a struct that only caches the information of a
|
||||
// ChannelEdgePolicy that we actually use for pathfinding and therefore need to
|
||||
// store in the cache.
|
||||
type CachedEdgePolicy 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
|
||||
|
||||
// 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
|
||||
|
||||
// ToNodePubKey is a function that returns the to node of a policy.
|
||||
// Since we only ever store the inbound policy, this is always the node
|
||||
// that we query the channels for in ForEachChannel(). Therefore, we can
|
||||
// save a lot of space by not storing this information in the memory and
|
||||
// instead just set this function when we copy the policy from cache in
|
||||
// ForEachChannel().
|
||||
ToNodePubKey func() route.Vertex
|
||||
|
||||
// ToNodeFeatures are the to node's features. They are never set while
|
||||
// the edge is in the cache, only on the copy that is returned in
|
||||
// ForEachChannel().
|
||||
ToNodeFeatures *lnwire.FeatureVector
|
||||
}
|
||||
|
||||
// ComputeFee computes the fee to forward an HTLC of `amt` milli-satoshis over
|
||||
// the passed active payment channel. This value is currently computed as
|
||||
// specified in BOLT07, but will likely change in the near future.
|
||||
func (c *CachedEdgePolicy) ComputeFee(
|
||||
amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {
|
||||
|
||||
return c.FeeBaseMSat + (amt*c.FeeProportionalMillionths)/feeRateParts
|
||||
}
|
||||
|
||||
// ComputeFeeFromIncoming computes the fee to forward an HTLC given the incoming
|
||||
// amount.
|
||||
func (c *CachedEdgePolicy) ComputeFeeFromIncoming(
|
||||
incomingAmt lnwire.MilliSatoshi) lnwire.MilliSatoshi {
|
||||
|
||||
return incomingAmt - divideCeil(
|
||||
feeRateParts*(incomingAmt-c.FeeBaseMSat),
|
||||
feeRateParts+c.FeeProportionalMillionths,
|
||||
)
|
||||
}
|
||||
|
||||
// NewCachedPolicy turns a full policy into a minimal one that can be cached.
|
||||
func NewCachedPolicy(policy *ChannelEdgePolicy) *CachedEdgePolicy {
|
||||
return &CachedEdgePolicy{
|
||||
ChannelID: policy.ChannelID,
|
||||
MessageFlags: policy.MessageFlags,
|
||||
ChannelFlags: policy.ChannelFlags,
|
||||
TimeLockDelta: policy.TimeLockDelta,
|
||||
MinHTLC: policy.MinHTLC,
|
||||
MaxHTLC: policy.MaxHTLC,
|
||||
FeeBaseMSat: policy.FeeBaseMSat,
|
||||
FeeProportionalMillionths: policy.FeeProportionalMillionths,
|
||||
}
|
||||
}
|
||||
|
||||
// DirectedChannel is a type that stores the channel information as seen from
|
||||
@ -58,10 +142,7 @@ type DirectedChannel struct {
|
||||
// In path finding, we're walking backward from the destination to the
|
||||
// source, so we're always interested in the edge that arrives to us
|
||||
// from the other node.
|
||||
InPolicy *models.CachedEdgePolicy
|
||||
|
||||
// Inbound fees of this node.
|
||||
InboundFee lnwire.Fee
|
||||
InPolicy *CachedEdgePolicy
|
||||
}
|
||||
|
||||
// DeepCopy creates a deep copy of the channel, including the incoming policy.
|
||||
@ -142,9 +223,9 @@ func (c *GraphCache) AddNode(tx kvdb.RTx, node GraphCacheNode) error {
|
||||
c.AddNodeFeatures(node)
|
||||
|
||||
return node.ForEachChannel(
|
||||
tx, func(tx kvdb.RTx, info *models.ChannelEdgeInfo,
|
||||
outPolicy *models.ChannelEdgePolicy,
|
||||
inPolicy *models.ChannelEdgePolicy) error {
|
||||
tx, func(tx kvdb.RTx, info *ChannelEdgeInfo,
|
||||
outPolicy *ChannelEdgePolicy,
|
||||
inPolicy *ChannelEdgePolicy) error {
|
||||
|
||||
c.AddChannel(info, outPolicy, inPolicy)
|
||||
|
||||
@ -157,8 +238,8 @@ func (c *GraphCache) AddNode(tx kvdb.RTx, node GraphCacheNode) error {
|
||||
// and policy 2 does not matter, the directionality is extracted from the info
|
||||
// and policy flags automatically. The policy will be set as the outgoing policy
|
||||
// on one node and the incoming policy on the peer's side.
|
||||
func (c *GraphCache) AddChannel(info *models.ChannelEdgeInfo,
|
||||
policy1 *models.ChannelEdgePolicy, policy2 *models.ChannelEdgePolicy) {
|
||||
func (c *GraphCache) AddChannel(info *ChannelEdgeInfo,
|
||||
policy1 *ChannelEdgePolicy, policy2 *ChannelEdgePolicy) {
|
||||
|
||||
if info == nil {
|
||||
return
|
||||
@ -190,7 +271,7 @@ func (c *GraphCache) AddChannel(info *models.ChannelEdgeInfo,
|
||||
// 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.ToNode != info.NodeKey2Bytes {
|
||||
if policy1.Node.PubKeyBytes != info.NodeKey2Bytes {
|
||||
fromNode, toNode = toNode, fromNode
|
||||
}
|
||||
isEdge1 := policy1.ChannelFlags&lnwire.ChanUpdateDirection == 0
|
||||
@ -198,7 +279,7 @@ func (c *GraphCache) AddChannel(info *models.ChannelEdgeInfo,
|
||||
}
|
||||
if policy2 != nil {
|
||||
fromNode, toNode := info.NodeKey2Bytes, info.NodeKey1Bytes
|
||||
if policy2.ToNode != info.NodeKey1Bytes {
|
||||
if policy2.Node.PubKeyBytes != info.NodeKey1Bytes {
|
||||
fromNode, toNode = toNode, fromNode
|
||||
}
|
||||
isEdge1 := policy2.ChannelFlags&lnwire.ChanUpdateDirection == 0
|
||||
@ -220,17 +301,9 @@ func (c *GraphCache) updateOrAddEdge(node route.Vertex, edge *DirectedChannel) {
|
||||
// of the from and to node is not strictly important. But we assume that a
|
||||
// channel edge was added beforehand so that the directed channel struct already
|
||||
// exists in the cache.
|
||||
func (c *GraphCache) UpdatePolicy(policy *models.ChannelEdgePolicy, fromNode,
|
||||
func (c *GraphCache) UpdatePolicy(policy *ChannelEdgePolicy, fromNode,
|
||||
toNode route.Vertex, edge1 bool) {
|
||||
|
||||
// Extract inbound fee if possible and available. If there is a decoding
|
||||
// error, ignore this policy.
|
||||
var inboundFee lnwire.Fee
|
||||
_, err := policy.ExtraOpaqueData.ExtractRecords(&inboundFee)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
|
||||
@ -251,18 +324,16 @@ func (c *GraphCache) UpdatePolicy(policy *models.ChannelEdgePolicy, fromNode,
|
||||
// policy for node 1.
|
||||
case channel.IsNode1 && edge1:
|
||||
channel.OutPolicySet = true
|
||||
channel.InboundFee = inboundFee
|
||||
|
||||
// This is node 2, and it is edge 2, so this is the outgoing
|
||||
// policy for node 2.
|
||||
case !channel.IsNode1 && !edge1:
|
||||
channel.OutPolicySet = true
|
||||
channel.InboundFee = inboundFee
|
||||
|
||||
// The other two cases left mean it's the inbound policy for the
|
||||
// node.
|
||||
default:
|
||||
channel.InPolicy = models.NewCachedPolicy(policy)
|
||||
channel.InPolicy = NewCachedPolicy(policy)
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,7 +380,7 @@ func (c *GraphCache) removeChannelIfFound(node route.Vertex, chanID uint64) {
|
||||
// UpdateChannel updates the channel edge information for a specific edge. We
|
||||
// expect the edge to already exist and be known. If it does not yet exist, this
|
||||
// call is a no-op.
|
||||
func (c *GraphCache) UpdateChannel(info *models.ChannelEdgeInfo) {
|
||||
func (c *GraphCache) UpdateChannel(info *ChannelEdgeInfo) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
@ -29,9 +28,9 @@ type node struct {
|
||||
pubKey route.Vertex
|
||||
features *lnwire.FeatureVector
|
||||
|
||||
edgeInfos []*models.ChannelEdgeInfo
|
||||
outPolicies []*models.ChannelEdgePolicy
|
||||
inPolicies []*models.ChannelEdgePolicy
|
||||
edgeInfos []*ChannelEdgeInfo
|
||||
outPolicies []*ChannelEdgePolicy
|
||||
inPolicies []*ChannelEdgePolicy
|
||||
}
|
||||
|
||||
func (n *node) PubKey() route.Vertex {
|
||||
@ -42,8 +41,8 @@ func (n *node) Features() *lnwire.FeatureVector {
|
||||
}
|
||||
|
||||
func (n *node) ForEachChannel(tx kvdb.RTx,
|
||||
cb func(kvdb.RTx, *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
|
||||
*models.ChannelEdgePolicy) error) error {
|
||||
cb func(kvdb.RTx, *ChannelEdgeInfo, *ChannelEdgePolicy,
|
||||
*ChannelEdgePolicy) error) error {
|
||||
|
||||
for idx := range n.edgeInfos {
|
||||
err := cb(
|
||||
@ -71,32 +70,34 @@ func TestGraphCacheAddNode(t *testing.T) {
|
||||
channelFlagA, channelFlagB = 1, 0
|
||||
}
|
||||
|
||||
outPolicy1 := &models.ChannelEdgePolicy{
|
||||
outPolicy1 := &ChannelEdgePolicy{
|
||||
ChannelID: 1000,
|
||||
ChannelFlags: lnwire.ChanUpdateChanFlags(channelFlagA),
|
||||
ToNode: nodeB,
|
||||
// Define an inbound fee.
|
||||
ExtraOpaqueData: []byte{
|
||||
253, 217, 3, 8, 0, 0, 0, 10, 0, 0, 0, 20,
|
||||
Node: &LightningNode{
|
||||
PubKeyBytes: nodeB,
|
||||
Features: lnwire.EmptyFeatureVector(),
|
||||
},
|
||||
}
|
||||
inPolicy1 := &models.ChannelEdgePolicy{
|
||||
inPolicy1 := &ChannelEdgePolicy{
|
||||
ChannelID: 1000,
|
||||
ChannelFlags: lnwire.ChanUpdateChanFlags(channelFlagB),
|
||||
ToNode: nodeA,
|
||||
Node: &LightningNode{
|
||||
PubKeyBytes: nodeA,
|
||||
Features: lnwire.EmptyFeatureVector(),
|
||||
},
|
||||
}
|
||||
node := &node{
|
||||
pubKey: nodeA,
|
||||
features: lnwire.EmptyFeatureVector(),
|
||||
edgeInfos: []*models.ChannelEdgeInfo{{
|
||||
edgeInfos: []*ChannelEdgeInfo{{
|
||||
ChannelID: 1000,
|
||||
// Those are direction independent!
|
||||
NodeKey1Bytes: pubKey1,
|
||||
NodeKey2Bytes: pubKey2,
|
||||
Capacity: 500,
|
||||
}},
|
||||
outPolicies: []*models.ChannelEdgePolicy{outPolicy1},
|
||||
inPolicies: []*models.ChannelEdgePolicy{inPolicy1},
|
||||
outPolicies: []*ChannelEdgePolicy{outPolicy1},
|
||||
inPolicies: []*ChannelEdgePolicy{inPolicy1},
|
||||
}
|
||||
cache := NewGraphCache(10)
|
||||
require.NoError(t, cache.AddNode(nil, node))
|
||||
@ -128,18 +129,8 @@ func TestGraphCacheAddNode(t *testing.T) {
|
||||
edges map[uint64]*DirectedChannel) error {
|
||||
|
||||
nodes[node] = struct{}{}
|
||||
for chanID, directedChannel := range edges {
|
||||
for chanID := range edges {
|
||||
chans[chanID] = struct{}{}
|
||||
|
||||
if node == nodeA {
|
||||
require.NotZero(
|
||||
t, directedChannel.InboundFee,
|
||||
)
|
||||
} else {
|
||||
require.Zero(
|
||||
t, directedChannel.InboundFee,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -153,8 +144,8 @@ func TestGraphCacheAddNode(t *testing.T) {
|
||||
runTest(pubKey2, pubKey1)
|
||||
}
|
||||
|
||||
func assertCachedPolicyEqual(t *testing.T, original *models.ChannelEdgePolicy,
|
||||
cached *models.CachedEdgePolicy) {
|
||||
func assertCachedPolicyEqual(t *testing.T, original *ChannelEdgePolicy,
|
||||
cached *CachedEdgePolicy) {
|
||||
|
||||
require.Equal(t, original.ChannelID, cached.ChannelID)
|
||||
require.Equal(t, original.MessageFlags, cached.MessageFlags)
|
||||
@ -168,6 +159,9 @@ func assertCachedPolicyEqual(t *testing.T, original *models.ChannelEdgePolicy,
|
||||
cached.FeeProportionalMillionths,
|
||||
)
|
||||
require.Equal(
|
||||
t, route.Vertex(original.ToNode), cached.ToNodePubKey(),
|
||||
t,
|
||||
route.Vertex(original.Node.PubKeyBytes),
|
||||
cached.ToNodePubKey(),
|
||||
)
|
||||
require.Equal(t, original.Node.Features, cached.ToNodeFeatures)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,141 +0,0 @@
|
||||
package graphsession
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
)
|
||||
|
||||
// Factory implements the routing.GraphSessionFactory and can be used to start
|
||||
// a session with a ReadOnlyGraph.
|
||||
type Factory struct {
|
||||
graph ReadOnlyGraph
|
||||
}
|
||||
|
||||
// NewGraphSessionFactory constructs a new Factory which can then be used to
|
||||
// start a new session.
|
||||
func NewGraphSessionFactory(graph ReadOnlyGraph) routing.GraphSessionFactory {
|
||||
return &Factory{
|
||||
graph: graph,
|
||||
}
|
||||
}
|
||||
|
||||
// NewGraphSession will produce a new Graph to use for a path-finding session.
|
||||
// It returns the Graph along with a call-back that must be called once Graph
|
||||
// access is complete. This call-back will close any read-only transaction that
|
||||
// was created at Graph construction time.
|
||||
//
|
||||
// NOTE: This is part of the routing.GraphSessionFactory interface.
|
||||
func (g *Factory) NewGraphSession() (routing.Graph, func() error, error) {
|
||||
tx, err := g.graph.NewPathFindTx()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
session := &session{
|
||||
graph: g.graph,
|
||||
tx: tx,
|
||||
}
|
||||
|
||||
return session, session.close, nil
|
||||
}
|
||||
|
||||
// A compile-time check to ensure that Factory implements the
|
||||
// routing.GraphSessionFactory interface.
|
||||
var _ routing.GraphSessionFactory = (*Factory)(nil)
|
||||
|
||||
// session is an implementation of the routing.Graph interface where the same
|
||||
// read-only transaction is held across calls to the graph and can be used to
|
||||
// access the backing channel graph.
|
||||
type session struct {
|
||||
graph graph
|
||||
tx kvdb.RTx
|
||||
}
|
||||
|
||||
// NewRoutingGraph constructs a session that which does not first start a
|
||||
// read-only transaction and so each call on the routing.Graph will create a
|
||||
// new transaction.
|
||||
func NewRoutingGraph(graph ReadOnlyGraph) routing.Graph {
|
||||
return &session{
|
||||
graph: graph,
|
||||
}
|
||||
}
|
||||
|
||||
// close closes the read-only transaction being used to access the backing
|
||||
// graph. If no transaction was started then this is a no-op.
|
||||
func (g *session) close() error {
|
||||
if g.tx == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := g.tx.Rollback()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error closing db tx: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForEachNodeChannel calls the callback for every channel of the given node.
|
||||
//
|
||||
// NOTE: Part of the routing.Graph interface.
|
||||
func (g *session) ForEachNodeChannel(nodePub route.Vertex,
|
||||
cb func(channel *channeldb.DirectedChannel) error) error {
|
||||
|
||||
return g.graph.ForEachNodeDirectedChannel(g.tx, 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 routing.Graph interface.
|
||||
func (g *session) FetchNodeFeatures(nodePub route.Vertex) (
|
||||
*lnwire.FeatureVector, error) {
|
||||
|
||||
return g.graph.FetchNodeFeatures(nodePub)
|
||||
}
|
||||
|
||||
// A compile-time check to ensure that *session implements the
|
||||
// routing.Graph interface.
|
||||
var _ routing.Graph = (*session)(nil)
|
||||
|
||||
// ReadOnlyGraph is a graph extended with a call to create a new read-only
|
||||
// transaction that can then be used to make further queries to the graph.
|
||||
type ReadOnlyGraph interface {
|
||||
// NewPathFindTx returns a new read transaction that can be used for a
|
||||
// single path finding session. Will return nil if the graph cache is
|
||||
// enabled.
|
||||
NewPathFindTx() (kvdb.RTx, error)
|
||||
|
||||
graph
|
||||
}
|
||||
|
||||
// graph describes the API necessary for a graph source to have access to on a
|
||||
// database implementation, like channeldb.ChannelGraph, in order to be used by
|
||||
// the Router for pathfinding.
|
||||
type graph interface {
|
||||
// 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 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.
|
||||
//
|
||||
// NOTE: if a nil tx is provided, then it is expected that the
|
||||
// implementation create a read only tx.
|
||||
ForEachNodeDirectedChannel(tx kvdb.RTx, node route.Vertex,
|
||||
cb func(channel *channeldb.DirectedChannel) error) error
|
||||
|
||||
// FetchNodeFeatures returns the features of a given node. If no
|
||||
// features are known for the node, an empty feature vector is returned.
|
||||
FetchNodeFeatures(node route.Vertex) (*lnwire.FeatureVector, error)
|
||||
}
|
||||
|
||||
// A compile-time check to ensure that *channeldb.ChannelGraph implements the
|
||||
// graph interface.
|
||||
var _ graph = (*channeldb.ChannelGraph)(nil)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -40,7 +40,7 @@ func deserializeCloseChannelSummaryV6(r io.Reader) (*ChannelCloseSummary, error)
|
||||
|
||||
// Finally, we'll attempt to read the next unrevoked commitment point
|
||||
// for the remote party. If we closed the channel before receiving a
|
||||
// channel_ready message, then this can be nil. As a result, we'll use
|
||||
// funding locked message, then this can be nil. As a result, we'll use
|
||||
// the same technique to read the field, only if there's still data
|
||||
// left in the buffer.
|
||||
err = ReadElements(r, &c.RemoteNextRevocation)
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration16"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration24"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration30"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration31"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
)
|
||||
@ -41,6 +40,5 @@ func UseLogger(logger btclog.Logger) {
|
||||
migration16.UseLogger(logger)
|
||||
migration24.UseLogger(logger)
|
||||
migration30.UseLogger(logger)
|
||||
migration31.UseLogger(logger)
|
||||
kvdb.UseLogger(logger)
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package lnwire
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// AnnounceSignatures is a direct message between two endpoints of a
|
||||
@ -65,7 +66,7 @@ func (a *AnnounceSignatures) Decode(r io.Reader, pver uint32) error {
|
||||
// we'll collect the remainder into the ExtraOpaqueData field. If there
|
||||
// aren't any bytes, then we'll snip off the slice to avoid carrying
|
||||
// around excess capacity.
|
||||
a.ExtraOpaqueData, err = io.ReadAll(r)
|
||||
a.ExtraOpaqueData, err = ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package lnwire
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
)
|
||||
@ -88,7 +89,7 @@ func (a *ChannelAnnouncement) Decode(r io.Reader, pver uint32) error {
|
||||
// we'll collect the remainder into the ExtraOpaqueData field. If there
|
||||
// aren't any bytes, then we'll snip off the slice to avoid carrying
|
||||
// around excess capacity.
|
||||
a.ExtraOpaqueData, err = io.ReadAll(r)
|
||||
a.ExtraOpaqueData, err = ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
)
|
||||
@ -159,7 +160,7 @@ func (a *ChannelUpdate) Decode(r io.Reader, pver uint32) error {
|
||||
// we'll collect the remainder into the ExtraOpaqueData field. If there
|
||||
// aren't any bytes, then we'll snip off the slice to avoid carrying
|
||||
// around excess capacity.
|
||||
a.ExtraOpaqueData, err = io.ReadAll(r)
|
||||
a.ExtraOpaqueData, err = ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"unicode/utf8"
|
||||
)
|
||||
@ -126,7 +127,7 @@ func (a *NodeAnnouncement) Decode(r io.Reader, pver uint32) error {
|
||||
// we'll collect the remainder into the ExtraOpaqueData field. If there
|
||||
// aren't any bytes, then we'll snip off the slice to avoid carrying
|
||||
// around excess capacity.
|
||||
a.ExtraOpaqueData, err = io.ReadAll(r)
|
||||
a.ExtraOpaqueData, err = ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1212,7 +1212,7 @@ func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) {
|
||||
// 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 error len: %w", err)
|
||||
return nil, fmt.Errorf("unable to read error len: %v", err)
|
||||
}
|
||||
if failureLength > FailureMessageLength {
|
||||
return nil, fmt.Errorf("failure message is too "+
|
||||
@ -1236,7 +1236,7 @@ func DecodeFailureMessage(r io.Reader, pver uint32) (FailureMessage, error) {
|
||||
// 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)
|
||||
return nil, fmt.Errorf("unable to read failure code: %v", err)
|
||||
}
|
||||
failCode := FailCode(binary.BigEndian.Uint16(codeBytes[:]))
|
||||
|
||||
@ -1244,7 +1244,7 @@ func DecodeFailureMessage(r io.Reader, pver uint32) (FailureMessage, error) {
|
||||
// additional data if needed.
|
||||
failure, err := makeEmptyOnionError(failCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to make empty error: %w", err)
|
||||
return nil, fmt.Errorf("unable to make empty error: %v", err)
|
||||
}
|
||||
|
||||
// Finally, if this failure has a payload, then we'll read that now as
|
||||
|
||||
@ -223,8 +223,7 @@ func decodeShortChanIDs(r io.Reader) (ShortChanIDEncoding, []ShortChannelID, err
|
||||
N: maxZlibBufSize,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("unable to create zlib "+
|
||||
"reader: %w", err)
|
||||
return 0, nil, fmt.Errorf("unable to create zlib reader: %v", err)
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user